فهرست منبع

AMBARI-6642. FE: Ambari installer wizard should use /recommendations API to determine component layout

Srimanth Gunturi 10 سال پیش
والد
کامیت
9986f6c158

+ 149 - 0
ambari-web/app/assets/data/stacks/HDP-2.1/recommendations.json

@@ -0,0 +1,149 @@
+{
+  "resources" : [
+{
+  "Versions": {
+    "stack_name": "HDP",
+    "stack_version": "2.1.1"
+  },
+  "hosts": ["dev1.hortonworks.com", "dev2.hortonworks.com", "dev3.hortonworks.com"],
+  "services": ["FALCON", "FLUME", "GANGLIA", "HBASE", "HCATALOG", "HDFS", "HIVE", "MAPREDUCE2", "NAGIOS", "OOZIE", "PIG", "SQOOP", "STORM", "TEZ", "WEBCHAT", "YARN", "ZOOKEEPER"],
+  "recommendations": {
+    "blueprint": {
+      "configurations": {
+        "global": {
+          "properties": {
+            "hbase_user": "hbase",
+            "clientPort": "2181",
+            "hadoop_heapsize": "1024"
+          }
+        },
+        "core-site": { },
+        "hdfs-site": { },
+        "yarn-site": { },
+        "hbase-site": { }
+      },
+      "host_groups": [
+        {
+          "name": "host-group-1",
+          "components": [
+            {
+              "name": "NAMENODE"
+            },
+            {
+              "name": "NAGIOS_SERVER"
+            },
+            {
+              "name": "GANGLIA_SERVER"
+            },
+            {
+              "name": "HBASE_MASTER"
+            },
+            {
+              "name": "ZOOKEEPER_SERVER"
+            },
+            {
+              "name": "DRPC_SERVER"
+            },
+            {
+              "name": "NIMBUS"
+            },
+            {
+              "name": "STORM_REST_API"
+            },
+            {
+              "name": "STORM_UI_SERVER"
+            }
+          ]
+        },
+        {
+          "name": "host-group-2",
+          "components": [
+            {
+              "name": "SECONDARY_NAMENODE"
+            },
+            {
+              "name": "HISTORYSERVER"
+            },
+            {
+              "name": "APP_TIMELINE_SERVER"
+            },
+            {
+              "name": "RESOURCEMANAGER"
+            },
+            {
+              "name": "HIVE_METASTORE"
+            },
+            {
+              "name": "HIVE_SERVER"
+            },
+            {
+              "name": "WEBHCAT_SERVER"
+            },
+            {
+              "name": "OOZIE_SERVER"
+            },
+            {
+              "name": "ZOOKEEPER_SERVER"
+            },
+            {
+              "name": "FALCON_SERVER"
+            }
+          ]
+        },
+        {
+          "name": "host-group-3",
+          "components": [
+            {
+              "name": "ZOOKEEPER_SERVER"
+            },
+            {
+              "name": "DATANODE"
+            },
+            {
+              "name": "NODEMANAGER"
+            },
+            {
+              "name": "HBASE_REGIONSERVER"
+            },
+            {
+              "name": "SUPERVISOR"
+            },
+            {
+              "name": "CLIENT"
+            }
+          ]
+        }
+      ]
+    },
+    "blueprint_cluster_binding": {
+      "host_groups": [
+        {
+          "name": "host-group-1",
+          "hosts": [
+            {
+              "fqdn": "hwa-cls2-m.viacode.com"
+            }
+          ]
+        },
+        {
+          "name": "host-group-2",
+          "hosts": [
+            {
+              "fqdn": "hwa-cls2-s1.viacode.com"
+            }
+          ]
+        },
+        {
+          "name": "host-group-3",
+          "hosts": [
+            {
+              "fqdn": "hwa-cls2-s2.viacode.com"
+            }
+          ]
+        }
+      ]
+    }
+  }
+}
+]
+}

+ 7 - 0
ambari-web/app/controllers/installer.js

@@ -732,6 +732,13 @@ App.InstallerController = App.WizardController.extend({
       var step = this.get('isStepDisabled').findProperty('step', i);
       var step = this.get('isStepDisabled').findProperty('step', i);
       step.set('value', true);
       step.set('value', true);
     }
     }
+  },
+
+  /**
+   * Clear loaded recommendations
+   */
+  clearRecommendations: function() {
+    this.set('recommendations', undefined)
   }
   }
 });
 });
 
 

+ 137 - 143
ambari-web/app/controllers/wizard/step5_controller.js

@@ -20,7 +20,6 @@ var App = require('app');
 var numberUtils = require('utils/number_utils');
 var numberUtils = require('utils/number_utils');
 
 
 App.WizardStep5Controller = Em.Controller.extend({
 App.WizardStep5Controller = Em.Controller.extend({
-
   name: "wizardStep5Controller",
   name: "wizardStep5Controller",
 
 
   /**
   /**
@@ -215,11 +214,20 @@ App.WizardStep5Controller = Em.Controller.extend({
     console.log("WizardStep5Controller: Loading step5: Assign Masters");
     console.log("WizardStep5Controller: Loading step5: Assign Masters");
     this.clearStep();
     this.clearStep();
     this.renderHostInfo();
     this.renderHostInfo();
-    this.renderComponents(this.loadComponents());
-    this.get('addableComponents').forEach(function (componentName) {
-      this.updateComponent(componentName);
-    }, this);
-    if (!this.get("selectedServicesMasters").filterProperty('isInstalled', false).length) {
+    this.loadComponentRecommendations(this.loadStepCallback);
+  },
+
+  /**
+   * Callback after load controller data (hosts, host components etc)
+   * @method loadStepCallback
+   */
+  loadStepCallback: function(components, self) {
+    self.renderComponents(components);
+
+    self.get('addableComponents').forEach(function (componentName) {
+      self.updateComponent(componentName);
+    }, self);
+    if (!self.get("selectedServicesMasters").filterProperty('isInstalled', false).length) {
       console.log('no master components to add');
       console.log('no master components to add');
       App.router.send('next');
       App.router.send('next');
     }
     }
@@ -289,81 +297,144 @@ App.WizardStep5Controller = Em.Controller.extend({
   },
   },
 
 
   /**
   /**
-   * Load services info to appropriate variable and return masterComponentHosts
+   * Get recommendations info from API
+   * @return {undefined}
+   */
+  loadComponentRecommendations: function(callback) {
+    var self = this;
+
+    if (App.router.get('installerController.recommendations') !== undefined) {
+      // Don't do AJAX call if recommendations has been already received
+      // But if user returns to previous step (selecting services), stored recommendations will be cleared in routers' next handler and AJAX call will be made again
+      callback(self.createComponentInstalltationObjects(), self);
+    } else {
+      var selectedServices = App.StackService.find().filterProperty('isSelected').mapProperty('serviceName');
+      var installedServices = App.StackService.find().filterProperty('isInstalled').mapProperty('serviceName');
+      var services = installedServices.concat(selectedServices).uniq();
+
+      var hostNames = self.get('hosts').mapProperty('host_name');
+
+      return App.ajax.send({
+        name: 'wizard.step5.recommendations',
+        sender: self,
+        data: {
+          stackVersionUrl: App.get('stackVersionURL'),
+          hosts: hostNames,
+          services: services
+        },
+        success: 'loadRecommendationsSuccessCallback'
+      }).
+        retry({
+          times: App.maxRetries,
+          timeout: App.timeout
+        }).
+        then(function () {
+          callback(self.createComponentInstalltationObjects(), self);
+        },
+        function () {
+          App.showReloadPopup();
+          console.log('Load recommendations failed');
+        }
+      );
+    }
+  },
+
+  /**
+   * Create components for displaying component-host comboboxes in UI assign dialog
+   * expects installerController.recommendations will be filled with recommendations API call result
    * @return {Object[]}
    * @return {Object[]}
    */
    */
-  loadComponents: function () {
-    var selectedServices = App.StackService.find().filterProperty('isSelected').mapProperty('serviceName');
-    var installedServices = App.StackService.find().filterProperty('isInstalled').mapProperty('serviceName');
-    var services = installedServices.concat(selectedServices).uniq();
-    var selectedNotInstalledServices = this.get('content.services').filterProperty('isSelected').filterProperty('isInstalled', false).mapProperty('serviceName');
+  createComponentInstalltationObjects: function() {
+    var self = this;
 
 
     var masterComponents = [];
     var masterComponents = [];
-    //get full list from mock data
-    if (this.get('isAddServiceWizard')) {
+    if (self.get('isAddServiceWizard')) {
       masterComponents = App.StackServiceComponent.find().filterProperty('isShownOnAddServiceAssignMasterPage');
       masterComponents = App.StackServiceComponent.find().filterProperty('isShownOnAddServiceAssignMasterPage');
     } else {
     } else {
       masterComponents = App.StackServiceComponent.find().filterProperty('isShownOnInstallerAssignMasterPage');
       masterComponents = App.StackServiceComponent.find().filterProperty('isShownOnInstallerAssignMasterPage');
     }
     }
-    var masterHosts = this.get('content.masterComponentHosts'); //saved to local storage info
 
 
-    var resultComponents = [];
+    var masterHosts = self.get('content.masterComponentHosts'); //saved to local storage info
+    var selectedNotInstalledServices = self.get('content.services').filterProperty('isSelected').filterProperty('isInstalled', false).mapProperty('serviceName');
+    var recommendations = App.router.get('installerController.recommendations');
 
 
-    for (var index = 0; index < services.length; index++) {
-      var componentInfo = masterComponents.filterProperty('serviceName', services[index]);
-      // If service is already installed and not being added as a new service then render on UI only those master components
-      // that have already installed hostComponents.
-      // NOTE: On upgrade there might be a prior installed service with non-installed newly introduced serviceComponent
-      var isNotSelectedService = !selectedNotInstalledServices.contains(services[index]);
-      if (isNotSelectedService) {
-        componentInfo = componentInfo.filter(function (_component) {
-          return App.HostComponent.find().someProperty('componentName',_component.get('componentName'));
+    var resultComponents = [];
+    var multipleComponentHasBeenAdded = {};
+
+    recommendations.blueprint.host_groups.forEach(function(host_group) {
+      var hosts = recommendations.blueprint_cluster_binding.host_groups.findProperty('name', host_group.name).hosts;
+
+      hosts.forEach(function(host) {
+        host_group.components.forEach(function(component) {
+          var willBeAdded = true;
+          var fullComponent = masterComponents.findProperty('componentName', component.name);
+          // If it's master component which should be shown
+          if (fullComponent) {
+            // If service is already installed and not being added as a new service then render on UI only those master components
+            // that have already installed hostComponents.
+            // NOTE: On upgrade there might be a prior installed service with non-installed newly introduced serviceComponent
+            var isNotSelectedService = !selectedNotInstalledServices.contains(fullComponent.get('serviceName'));
+            if (isNotSelectedService) {
+              willBeAdded = App.HostComponent.find().someProperty('componentName', component.name);
+            }
+
+            if (willBeAdded) {
+              var savedComponents = masterHosts.filterProperty('component', component.name);
+
+              if (self.get('multipleComponents').contains(component.name) && savedComponents.length > 0) {
+                if (!multipleComponentHasBeenAdded[component.name]) {
+                  multipleComponentHasBeenAdded[component.name] = true;
+
+                  savedComponents.forEach(function(saved) {
+                    resultComponents.push(self.createComponentInstalltationObject(fullComponent, host.fqdn, saved));
+                  });
+                }
+              } else {
+                var savedComponent = masterHosts.findProperty('component', component.name);
+                resultComponents.push(self.createComponentInstalltationObject(fullComponent, host.fqdn, savedComponent));
+              }
+            }
+          }
         });
         });
-      }
+      });
+    });
+    return resultComponents;
+  },
 
 
-      componentInfo.forEach(function (_componentInfo) {
-        if (this.get('multipleComponents').contains(_componentInfo.get('componentName'))) {
-          var savedComponents = masterHosts.filterProperty('component', _componentInfo.get('componentName'));
-          if (savedComponents.length) {
-            savedComponents.forEach(function (item) {
-              var multipleMasterHost = {};
-              multipleMasterHost.component_name = _componentInfo.get('componentName');
-              multipleMasterHost.display_name = _componentInfo.get('displayName');
-              multipleMasterHost.selectedHost = item.hostName;
-              multipleMasterHost.serviceId = services[index];
-              multipleMasterHost.isInstalled = item.isInstalled;
-              multipleMasterHost.isServiceCoHost = false;
-              resultComponents.push(multipleMasterHost);
-            })
-          } else {
-            var multipleMasterHosts = this.selectHost(_componentInfo.get('componentName'));
-            multipleMasterHosts.forEach(function (_host) {
-              var multipleMasterHost = {};
-              multipleMasterHost.component_name = _componentInfo.get('componentName');
-              multipleMasterHost.display_name = _componentInfo.get('displayName');
-              multipleMasterHost.selectedHost = _host;
-              multipleMasterHost.serviceId = services[index];
-              multipleMasterHost.isInstalled = false;
-              multipleMasterHost.isServiceCoHost = false;
-              resultComponents.push(multipleMasterHost);
-            });
+  /**
+   * Create component for displaying component-host comboboxes in UI assign dialog
+   * @param fullComponent - full component description
+   * @param hostName - host fqdn where component will be installed
+   * @param savedComponent - the same object which function returns but created before
+   * @return {Object}
+   */
+  createComponentInstalltationObject: function(fullComponent, hostName, savedComponent) {
+    var componentName = fullComponent.get('componentName');
 
 
-          }
-        } else {
-          var savedComponent = masterHosts.findProperty('component', _componentInfo.get('componentName'));
-          var componentObj = {};
-          componentObj.component_name = _componentInfo.get('componentName');
-          componentObj.display_name = _componentInfo.get('displayName');
-          componentObj.selectedHost = savedComponent ? savedComponent.hostName : this.selectHost(_componentInfo.get('componentName'));   // call the method that plays selectNode algorithm or fetches from server
-          componentObj.isInstalled = savedComponent ? savedComponent.isInstalled : false;
-          componentObj.serviceId = services[index];
-          componentObj.isServiceCoHost = App.StackServiceComponent.find().findProperty('componentName', _componentInfo.get('componentName')).get('isCoHostedComponent') && !this.get('isReassignWizard');
-          resultComponents.push(componentObj);
-        }
-      }, this);
+    var componentObj = {};
+    componentObj.component_name = componentName;
+    componentObj.display_name = fullComponent.get('displayName');
+    componentObj.serviceId = fullComponent.get('serviceName');
+    componentObj.isServiceCoHost = App.StackServiceComponent.find().findProperty('componentName', componentName).get('isCoHostedComponent') && !this.get('isReassignWizard');
+
+    if (savedComponent) {
+      componentObj.selectedHost = savedComponent.hostName;
+      componentObj.isInstalled = savedComponent.isInstalled;
+    } else {
+      componentObj.selectedHost = hostName;
+      componentObj.isInstalled = false;
     }
     }
 
 
-    return resultComponents;
+    return componentObj;
+  },
+
+  /**
+   * Success-callback for recommendations request
+   * @param {object} data
+   * @method loadRecommendationsSuccessCallback
+   */
+  loadRecommendationsSuccessCallback: function (data) {
+    App.router.set('installerController.recommendations', data.resources[0].recommendations);
   },
   },
 
 
   /**
   /**
@@ -429,83 +500,6 @@ App.WizardStep5Controller = Em.Controller.extend({
     }, this);
     }, this);
   }.observes('selectedServicesMasters.@each.selectedHost'),
   }.observes('selectedServicesMasters.@each.selectedHost'),
 
 
-  /**
-   * select and return host for component by scheme
-   * Scheme is an object that has keys which compared to number of hosts,
-   * if key more that number of hosts, then return value of that key.
-   * Value is index of host in hosts array.
-   *
-   * @param {object} componentName
-   * @param {object} hosts
-   * @return {string}
-   * @method getHostForComponent
-   */
-  getHostForComponent: function (componentName, hosts) {
-    var component = App.StackServiceComponent.find().findProperty('componentName', componentName);
-    if (component) {
-      var selectionScheme = App.StackServiceComponent.find().findProperty('componentName', componentName).get('selectionSchemeForMasterComponent');
-    } else {
-      return hosts[0];
-    }
-
-    if (hosts.length === 1 || $.isEmptyObject(selectionScheme)) {
-      return hosts[0];
-    } else {
-      for (var i in selectionScheme) {
-        if (window.isFinite(i)) {
-          if (hosts.length < window.parseInt(i)) {
-            return hosts[selectionScheme[i]];
-          }
-        }
-      }
-      return hosts[selectionScheme['else']]
-    }
-  },
-
-  /**
-   * Get list of host names for master component with multiple instances
-   * @param {Object} component
-   * @param {Object} hosts
-   * @returns {string[]}
-   * @method getHostsForComponent
-   */
-  getHostsForComponent: function (component, hosts) {
-    var defaultNoOfMasterHosts = component.get('defaultNoOfMasterHosts');
-    var masterHosts = [];
-    if (hosts.length < defaultNoOfMasterHosts) {
-      defaultNoOfMasterHosts = hosts.length;
-    }
-    for (var index = 0; index < defaultNoOfMasterHosts; index++) {
-      masterHosts.push(hosts[index]);
-    }
-    return masterHosts;
-  },
-
-  /**
-   * Return hostName of masterNode for specified service
-   * @param componentName
-   * @return {string|string[]}
-   * @method selectHost
-   */
-  selectHost: function (componentName) {
-    var component = App.StackServiceComponent.find().findProperty('componentName', componentName);
-    var hostNames = this.get('hosts').mapProperty('host_name');
-    if (hostNames.length > 1 && App.StackServiceComponent.find().filterProperty('isNotPreferableOnAmbariServerHost').mapProperty('componentName').contains(componentName)) {
-      hostNames = this.get('hosts').mapProperty('host_name').filter(function (item) {
-        return item !== location.hostname;
-      }, this);
-    }
-    if (this.get('multipleComponents').contains(componentName)) {
-      if (component.get('defaultNoOfMasterHosts') > 1) {
-        return this.getHostsForComponent(component, hostNames);
-      } else {
-        return [this.getHostForComponent(componentName, hostNames)];
-      }
-    } else {
-      return this.getHostForComponent(componentName, hostNames);
-    }
-  },
-
   /**
   /**
    * On change callback for inputs
    * On change callback for inputs
    * @param {string} componentName
    * @param {string} componentName

+ 26 - 20
ambari-web/app/controllers/wizard/step6_controller.js

@@ -345,30 +345,36 @@ App.WizardStep6Controller = Em.Controller.extend({
     var headers = this.get('headers');
     var headers = this.get('headers');
     var slaveComponents = this.get('content.slaveComponentHosts');
     var slaveComponents = this.get('content.slaveComponentHosts');
     if (!slaveComponents) { // we are at this page for the first time
     if (!slaveComponents) { // we are at this page for the first time
-      var client_is_set = false;
+      var recommendations = App.router.get('installerController.recommendations');
+      // Get all host-component pairs from recommendations
+      var componentHostPairs = recommendations.blueprint.host_groups.map(function(group) {
+        return group.components.map(function(component) {
+          return recommendations.blueprint_cluster_binding.host_groups.findProperty('name', group.name).hosts.map(function(host) {
+            return { component: component.name, host: host.fqdn};
+          });
+        });
+      });
+
+      // Flatten results twice because of two map() call before
+      componentHostPairs = [].concat.apply([], componentHostPairs);
+      componentHostPairs = [].concat.apply([], componentHostPairs);
+
+      var clientComponents = App.get('components.clients');
+
+
       hostsObj.forEach(function (host) {
       hostsObj.forEach(function (host) {
         var checkboxes = host.get('checkboxes');
         var checkboxes = host.get('checkboxes');
-        checkboxes.setEach('checked', !host.hasMaster);
-        checkboxes.setEach('isInstalled', false);
-        checkboxes.findProperty('title', headers.findProperty('name', 'CLIENT').get('label')).set('checked', false);
-        // First not Master should have Client (only first!)
-        if (!client_is_set) {
-          var dfs = App.StackService.find().findProperty('isPrimaryDFS');
-          if (dfs.get('isSelected') || dfs.get('isInstalled')) {
-            var checkboxServiceComponent = checkboxes.findProperty('title', headers.findProperty('name', dfs.get('serviceComponents').
-              findProperty('isShownOnInstallerSlaveClientPage').get('componentName')).get('label'));
-            if (checkboxServiceComponent && checkboxServiceComponent.get('checked')) {
-              checkboxes.findProperty('title', headers.findProperty('name', 'CLIENT').get('label')).set('checked', true);
-              client_is_set = true;
+        checkboxes.forEach(function(checkbox) {
+          var recommended = componentHostPairs.some(function(pair) {
+            var componentMatch = pair.component === checkbox.component;
+            if (checkbox.component === 'CLIENT' && !componentMatch) {
+              componentMatch = clientComponents.contains(pair.component);
             }
             }
-          }
-        }
+            return pair.host === host.hostName && componentMatch;
+          });
+          checkbox.checked = recommended;
+        });
       });
       });
-
-      if (this.get('isInstallerWizard') && hostsObj.everyProperty('hasMaster', true)) {
-        var lastHost = hostsObj[hostsObj.length - 1];
-        lastHost.get('checkboxes').setEach('checked', true);
-      }
     } else {
     } else {
       this.get('headers').forEach(function (header) {
       this.get('headers').forEach(function (header) {
         var nodes = slaveComponents.findProperty('componentName', header.get('name'));
         var nodes = slaveComponents.findProperty('componentName', header.get('name'));

+ 0 - 29
ambari-web/app/models/stack_service_component.js

@@ -192,10 +192,6 @@ App.StackServiceComponent = DS.Model.extend({
      }
      }
   }.property('componentName'),
   }.property('componentName'),
 
 
-  selectionSchemeForMasterComponent: function() {
-    return App.StackServiceComponent.selectionScheme(this.get('componentName'));
-  }.property('componentName'),
-
   /** @property {Boolean} coHostedComponents - components that are co-hosted with this component **/
   /** @property {Boolean} coHostedComponents - components that are co-hosted with this component **/
   coHostedComponents: function() {
   coHostedComponents: function() {
     var componentName = this.get('componentName');
     var componentName = this.get('componentName');
@@ -223,31 +219,6 @@ App.StackServiceComponent = DS.Model.extend({
 
 
 App.StackServiceComponent.FIXTURES = [];
 App.StackServiceComponent.FIXTURES = [];
 
 
-App.StackServiceComponent.selectionScheme = function (componentName){
-  switch (componentName) {
-    case 'NAMENODE' :
-      return {"else": 0};
-    case 'SECONDARY_NAMENODE' :
-      return {"else": 1};
-    case 'HBASE_MASTER':
-      return {"6": 0, "31": 2, "else": 3};
-    case 'JOBTRACKER':
-    case 'HISTORYSERVER':
-    case 'RESOURCEMANAGER':
-    case 'APP_TIMELINE_SERVER':
-      return {"31": 1, "else": 2};
-    case 'OOZIE_SERVER':
-    case 'FALCON_SERVER' :
-      return {"6": 1, "31": 2, "else": 3};
-    case 'HIVE_SERVER' :
-    case 'HIVE_METASTORE' :
-    case 'WEBHCAT_SERVER' :
-      return {"6": 1, "31": 2, "else": 4};
-    default:
-      return {"else": 0};
-  }
-};
-
 App.StackServiceComponent.coHost = {
 App.StackServiceComponent.coHost = {
   'HIVE_METASTORE': 'HIVE_SERVER',
   'HIVE_METASTORE': 'HIVE_SERVER',
   'WEBHCAT_SERVER': 'HIVE_SERVER'
   'WEBHCAT_SERVER': 'HIVE_SERVER'

+ 3 - 0
ambari-web/app/routes/add_service_routes.js

@@ -113,6 +113,9 @@ module.exports = App.WizardRoute.extend({
       addServiceController.saveServices(wizardStep4Controller);
       addServiceController.saveServices(wizardStep4Controller);
       addServiceController.saveClients(wizardStep4Controller);
       addServiceController.saveClients(wizardStep4Controller);
       addServiceController.setDBProperty('masterComponentHosts', undefined);
       addServiceController.setDBProperty('masterComponentHosts', undefined);
+
+      var installerController = router.get('installerController');
+      installerController.clearRecommendations(); // Force reload recommendation between steps 1 and 2
       router.transitionTo('step2');
       router.transitionTo('step2');
     }
     }
   }),
   }),

+ 1 - 0
ambari-web/app/routes/installer.js

@@ -249,6 +249,7 @@ module.exports = Em.Route.extend({
       controller.saveServices(wizardStep4Controller);
       controller.saveServices(wizardStep4Controller);
       controller.saveClients(wizardStep4Controller);
       controller.saveClients(wizardStep4Controller);
 
 
+      controller.clearRecommendations(); // Force reload recommendation between steps 4 and 5
       controller.setDBProperty('masterComponentHosts', undefined);
       controller.setDBProperty('masterComponentHosts', undefined);
       router.transitionTo('step5');
       router.transitionTo('step5');
     }
     }

+ 14 - 0
ambari-web/app/utils/ajax/ajax.js

@@ -1140,6 +1140,20 @@ var urls = {
     }
     }
   },
   },
 
 
+  'wizard.step5.recommendations': {
+    'real': '{stackVersionUrl}/recommendations',
+    'mock': '/data/stacks/HDP-2.1/recommendations.json',
+    'type': 'POST',
+    'format': function (data) {
+      return {
+        data: JSON.stringify({
+          hosts: data.hosts,
+          services: data.services
+        })
+      }
+    }
+  },
+
   'preinstalled.checks': {
   'preinstalled.checks': {
     'real':'/requests',
     'real':'/requests',
     'mock':'',
     'mock':'',