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

jgolieb pushed a commit to branch branch-feature-AMBARI-14714
in repository https://gitbox.apache.org/repos/asf/ambari.git


The following commit(s) were added to refs/heads/branch-feature-AMBARI-14714 by 
this push:
     new f898ba7  [AMBARI-24492] Refactor select mpacks view (#2099)
f898ba7 is described below

commit f898ba77e9ede0e4912f600310d564d47b3a9d3a
Author: Jason Golieb <[email protected]>
AuthorDate: Mon Aug 20 13:31:32 2018 -0400

    [AMBARI-24492] Refactor select mpacks view (#2099)
    
    * Refactored selectMpacks
    
    * Fixed some code changed in the recent merge so the unit test passes
---
 .../controller/internal/MpackResourceProvider.java |   5 +
 ambari-web/app/controllers/installer.js            |  80 +-
 ambari-web/app/controllers/wizard.js               | 132 +++-
 .../controllers/wizard/selectMpacks_controller.js  | 777 ++++++++++++++-----
 ambari-web/app/messages.js                         |   9 +-
 .../app/mixins/wizard/assign_master_components.js  |  78 +-
 ambari-web/app/models/stack_service.js             |   6 +-
 ambari-web/app/routes/installer.js                 |   5 +-
 ambari-web/app/styles/wizard.less                  |   8 +-
 ambari-web/app/templates/wizard/selectMpacks.hbs   | 128 +--
 .../app/templates/wizard/selectMpacks/mpack.hbs    |   8 +-
 .../app/templates/wizard/selectMpacks/service.hbs  |   6 +-
 .../{selectedMpackVersion.hbs => serviceGroup.hbs} |  32 +-
 .../selectMpacks/{usecase.hbs => useCase.hbs}      |  10 +-
 ambari-web/app/utils/ajax/ajax.js                  |  14 +-
 ambari-web/app/views/wizard/selectMpacks_view.js   |  36 +-
 ambari-web/test/controllers/installer_test.js      |   2 -
 .../main/service/add_controller_test.js            |  18 -
 .../test/controllers/wizard/selectMpacks_test.js   | 854 +++++++++++++--------
 .../wizard/step7/assign_master_controller_test.js  |   4 +-
 ambari-web/test/controllers/wizard_test.js         |  96 ++-
 .../mixins/wizard/assign_master_components_test.js |   2 +-
 22 files changed, 1564 insertions(+), 746 deletions(-)

diff --git 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/MpackResourceProvider.java
 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/MpackResourceProvider.java
index 8a93310..78ce1c6 100644
--- 
a/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/MpackResourceProvider.java
+++ 
b/ambari-server/src/main/java/org/apache/ambari/server/controller/internal/MpackResourceProvider.java
@@ -257,6 +257,11 @@ public class MpackResourceProvider extends 
AbstractControllerResourceProvider {
 
       for (MpackResponse response : responses) {
         Resource resource = setResources(response);
+        Set<String> requestIds = getRequestPropertyIds(request, predicate);
+        if (requestIds.contains(MODULES)) {
+               List<Module> modules = 
getManagementController().getModules(response.getId());
+               resource.setProperty(MODULES, modules);
+        }
         results.add(resource);
       }
     } else {
diff --git a/ambari-web/app/controllers/installer.js 
b/ambari-web/app/controllers/installer.js
index a93b1f4..9636b58 100644
--- a/ambari-web/app/controllers/installer.js
+++ b/ambari-web/app/controllers/installer.js
@@ -27,7 +27,7 @@ App.InstallerController = 
App.WizardController.extend(App.Persist, {
 
   isCheckInProgress: false,
 
-  totalSteps: function() {
+  totalSteps: function () {
     const steps = this.get("steps");
 
     if (steps) {
@@ -42,7 +42,7 @@ App.InstallerController = 
App.WizardController.extend(App.Persist, {
     "step2",
     "step3",
     "configureDownload",
-         "selectMpacks",
+    "selectMpacks",
     "customMpackRepos",
     "downloadMpacks",
     "customProductRepos",
@@ -115,6 +115,7 @@ App.InstallerController = 
App.WizardController.extend(App.Persist, {
     mpackServiceVersions: [],
     mpackServices: [],
     serviceGroups: [],
+    serviceInstances: [],
     // Tracks which steps have been saved before.
     // If you revisit a step, we will know if the step has been saved 
previously and we can warn about making changes.
     // If a previously saved step is changed, setStepSaved() will "unsave" all 
subsequent steps so we don't warn on every screen.
@@ -155,7 +156,12 @@ App.InstallerController = 
App.WizardController.extend(App.Persist, {
     'selectedServices',
     'selectedStack',
     'downloadConfig',
-    'stepsSavedState'
+    'stepsSavedState',
+    'serviceGroups',
+    'addedServiceGroups',
+    'serviceInstances',
+    'addedServiceInstances',
+    'registeredMpacks'
   ],
 
   init: function () {
@@ -182,10 +188,10 @@ App.InstallerController = 
App.WizardController.extend(App.Persist, {
     return jQuery.extend({}, this.get('clusterStatusTemplate'));
   },
 
-   /**
-   * Remove host from model. Used at <code>Confirm hosts(step2)</code> step
-   * @param hosts Array of hosts, which we want to delete
-   */
+  /**
+  * Remove host from model. Used at <code>Confirm hosts(step2)</code> step
+  * @param hosts Array of hosts, which we want to delete
+  */
   removeHosts: function (hosts) {
     var dbHosts = this.getDBProperty('hosts');
     hosts.forEach(function (_hostInfo) {
@@ -225,7 +231,7 @@ App.InstallerController = 
App.WizardController.extend(App.Persist, {
     App.MpackServiceMapper.map(serviceInfo);
   },
 
-  loadMpackServiceInfoError: function(request, status, error) {
+  loadMpackServiceInfoError: function (request, status, error) {
     const message = Em.I18n.t('installer.error.mpackServiceInfo');
 
     this.addError(message);
@@ -281,15 +287,19 @@ App.InstallerController = 
App.WizardController.extend(App.Persist, {
     for (var hostName in rawHosts) {
       var host = rawHosts[hostName];
       hosts.pushObject(Em.Object.create({
-          id: host.name,
-          hostName: host.name,
-          hostComponents: host.hostComponents || []
-        }
+        id: host.name,
+        hostName: host.name,
+        hostComponents: host.hostComponents || []
+      }
       ))
     }
     return hosts;
   }.property('content.hosts'),
 
+  allServiceGroups: function () {
+    return 
[].concat(this.get('content.serviceGroups')).concat(this.get('content.addedServiceGroups'));
+  }.property('content.serviceGroups', 'content.addedServiceGroups'),
+
   stacks: [],
 
   /**
@@ -501,9 +511,9 @@ App.InstallerController = 
App.WizardController.extend(App.Persist, {
    */
   loadMasterComponentHosts: function (lookInMemoryOnly) {
     var props = this.getDBProperties(['masterComponentHosts', 'hosts']),
-        masterComponentHosts = this.get("content.masterComponentHosts"),
-        hosts = props.hosts || {},
-        hostNames = Em.keys(hosts);
+      masterComponentHosts = this.get("content.masterComponentHosts"),
+      hosts = props.hosts || {},
+      hostNames = Em.keys(hosts);
 
     if (!lookInMemoryOnly && !masterComponentHosts) {
       masterComponentHosts = props.masterComponentHosts;
@@ -583,7 +593,7 @@ App.InstallerController = 
App.WizardController.extend(App.Persist, {
    */
   postVersionDefinitionFile: function (isXMLdata, data) {
     var dfd = $.Deferred();
-    var name = isXMLdata? 'wizard.step1.post_version_definition_file.xml' : 
'wizard.step1.post_version_definition_file.url';
+    var name = isXMLdata ? 'wizard.step1.post_version_definition_file.xml' : 
'wizard.step1.post_version_definition_file.url';
 
     App.ajax.send({
       name: name,
@@ -607,17 +617,17 @@ App.InstallerController = 
App.WizardController.extend(App.Persist, {
       // load the data info to display for details and contents panel
       data.VersionDefinition.id = Em.get(dataInfo, 
'data.VersionDefinition.available') || data.VersionDefinition.id;
       var response = {
-        id : data.VersionDefinition.id,
-        stackVersion : data.VersionDefinition.stack_version,
+        id: data.VersionDefinition.id,
+        stackVersion: data.VersionDefinition.stack_version,
         stackName: data.VersionDefinition.stack_name,
         type: data.VersionDefinition.type,
         stackNameVersion: data.VersionDefinition.stack_name + '-' + 
data.VersionDefinition.stack_version, /// HDP-2.3
         actualVersion: data.VersionDefinition.repository_version, /// 
2.3.4.0-3846
-        version: data.VersionDefinition.release ? 
data.VersionDefinition.release.version: null, /// 2.3.4.0
-        releaseNotes: data.VersionDefinition.release ? 
data.VersionDefinition.release.notes: null,
+        version: data.VersionDefinition.release ? 
data.VersionDefinition.release.version : null, /// 2.3.4.0
+        releaseNotes: data.VersionDefinition.release ? 
data.VersionDefinition.release.notes : null,
         displayName: data.VersionDefinition.release ? 
data.VersionDefinition.stack_name + '-' + 
data.VersionDefinition.release.version :
-        data.VersionDefinition.stack_name + '-' + 
data.VersionDefinition.repository_version, //HDP-2.3.4.0
-        repoVersionFullName : data.VersionDefinition.stack_name + '-' + 
data.VersionDefinition.repository_version,
+          data.VersionDefinition.stack_name + '-' + 
data.VersionDefinition.repository_version, //HDP-2.3.4.0
+        repoVersionFullName: data.VersionDefinition.stack_name + '-' + 
data.VersionDefinition.repository_version,
         osList: data.operating_systems,
         updateObj: data
       };
@@ -650,7 +660,7 @@ App.InstallerController = 
App.WizardController.extend(App.Persist, {
    */
   postVersionDefinitionFileStep8: function (isXMLdata, data) {
     var dfd = $.Deferred();
-    var name = isXMLdata == true? 
'wizard.step8.post_version_definition_file.xml' : 
'wizard.step8.post_version_definition_file';
+    var name = isXMLdata == true ? 
'wizard.step8.post_version_definition_file.xml' : 
'wizard.step8.post_version_definition_file';
     App.ajax.send({
       name: name,
       sender: this,
@@ -684,11 +694,11 @@ App.InstallerController = 
App.WizardController.extend(App.Persist, {
     params.dfd.reject(data);
     var header = 
Em.I18n.t('installer.step1.useLocalRepo.uploadFile.error.title');
     var body = '';
-    if(request && request.responseText) {
+    if (request && request.responseText) {
       try {
         var json = $.parseJSON(request.responseText);
         body = json.message;
-      } catch (err) {}
+      } catch (err) { }
     }
     App.db.setLocalRepoVDFData(undefined);
     App.showAlertPopup(header, body);
@@ -1003,11 +1013,21 @@ App.InstallerController = 
App.WizardController.extend(App.Persist, {
     ],
     'selectMpacks': [
       {
-        type: 'sync',
+        type: 'async',
         callback: function () {
-          this.load('selectedServices');
-          this.load('selectedMpacks');
-          this.load('advancedMode');
+          return this.loadRegisteredMpacks()
+            .then(() => {
+              this.load('selectedServices');
+              this.load('selectedMpacks');
+              this.load('addedServiceGroups');
+              this.load('addedServiceInstances');
+              this.load('advancedMode');
+              
+              const dfd = $.Deferred();
+              dfd.resolve();
+              return dfd.promise();
+            }
+          );
         }
       }
     ],
@@ -1015,7 +1035,6 @@ App.InstallerController = 
App.WizardController.extend(App.Persist, {
       {
         type: 'async',
         callback: function () {
-          this.loadRegisteredMpacks();
           return 
this.loadSelectedServiceInfo(this.getStepSavedState('customProductRepos'));
         }
       },
@@ -1037,7 +1056,6 @@ App.InstallerController = 
App.WizardController.extend(App.Persist, {
           this.loadConfirmedHosts();
           this.loadComponentsFromConfigs();
           this.loadRecommendations();
-          this.loadRegisteredMpacks();
         }
       }
     ],
diff --git a/ambari-web/app/controllers/wizard.js 
b/ambari-web/app/controllers/wizard.js
index 9ed1899..ca7a71e 100644
--- a/ambari-web/app/controllers/wizard.js
+++ b/ambari-web/app/controllers/wizard.js
@@ -46,7 +46,12 @@ App.WizardController = 
Em.Controller.extend(App.LocalStorage, App.ThemesMappingM
     'allHostNamesPattern',
     'serviceComponents',
     'fileNamesToUpdate',
-    'componentsFromConfigs'
+    'componentsFromConfigs',
+    'stepsSavedState',
+    'operatingSystems',
+    'repositories',
+    'selectedMpacks',
+    'selectedServices'
   ],
 
   sensibleConfigs: [
@@ -1389,6 +1394,53 @@ App.WizardController = 
Em.Controller.extend(App.LocalStorage, App.ThemesMappingM
   },
 
   /**
+   * Loads info about registered mpacks on the server into the controller's 
content.
+   * If the data is already loaded into the localStorage, it is copied from 
there.
+   * If not, it is loaded from the server and stored in both localStorage and 
the controller's content.
+   */
+  loadRegisteredMpacks: function () {
+    let dfd;
+    let registeredMpacks = this.getDBProperty('registeredMpacks');
+    
+    if (registeredMpacks) {
+      this.set('content.registeredMpacks', registeredMpacks);
+      
+      //TODO: keep doing this for now
+      registeredMpacks.forEach(rmp => {
+        App.stackMapper.map(JSON.parse(JSON.stringify(rmp)));
+      });
+
+      dfd = $.Deferred();
+      dfd.resolve();
+    } else {
+      dfd = App.ajax.send({
+        name: 'mpack.get_registered_mpacks',
+        sender: this,
+        success: 'loadRegisteredMpacksCallback',
+        error: 'defaultErrorCallback'
+      });
+    }
+
+    return dfd.promise();
+  },
+
+  loadRegisteredMpacksCallback: function (response) {
+    const registeredMpacks = response.items;
+
+    //TODO: keep doing this for now
+    registeredMpacks.forEach(rmp => {
+      App.stackMapper.map(JSON.parse(JSON.stringify(rmp)));
+    });
+ 
+    this.setDBProperty('registeredMpacks', registeredMpacks);
+    this.set('content.registeredMpacks', registeredMpacks);
+  },
+
+  defaultErrorCallback: function (jqXHR, ajaxOptions, error, opt) {
+    App.ajax.defaultErrorHandler(jqXHR, opt.url, opt.type, jqXHR.status);
+  },
+
+  /**
    * Load services data from server.
    */
   loadServicesFromServer: function () {
@@ -1440,7 +1492,7 @@ App.WizardController = 
Em.Controller.extend(App.LocalStorage, App.ThemesMappingM
         sender: this,
         data: {},
         success: 'loadHostsSuccessCallback',
-        error: 'loadHostsErrorCallback'
+        error: 'defaultErrorCallback'
       });
     }
     return dfd.promise();
@@ -1462,24 +1514,78 @@ App.WizardController = 
Em.Controller.extend(App.LocalStorage, App.ThemesMappingM
     this.set('content.hosts', installedHosts);
   },
 
-  loadHostsErrorCallback: function (jqXHR, ajaxOptions, error, opt) {
-    App.ajax.defaultErrorHandler(jqXHR, opt.url, opt.type, jqXHR.status);
+  /**
+   * Loads info about existing service groups on the server into the 
controller's content.
+   * If the data is already loaded into the localStorage, it is copied from 
there.
+   * If not, it is loaded from the server and stored in both localStorage and 
the controller's content.
+   */
+  loadServiceGroups: function () {
+    let dfd;
+    const serviceGroups = this.getDBProperty('serviceGroups');
+    const serviceInstances = this.getDBProperty('serviceInstances');
+
+    if (serviceGroups && serviceInstances) {
+      this.set('content.serviceGroups', serviceGroups);
+      this.set('content.serviceInstances', serviceInstances);
+      dfd = $.Deferred();
+      dfd.resolve();
+    } else {
+      dfd = App.ajax.send({
+        name: 'servicegroup.get_all_details',
+        sender: this,
+        success: 'loadServiceGroupsCallback',
+        error: 'loadServiceGroupsErrorCallback'
+      });
+    }
+
+    return dfd.promise();
   },
 
-  loadRegisteredMpacks: function () {
-    this.set('content.registeredMpacks', 
this.getDBProperty('registeredMpacks') || []);
-    const registeredMpacks = this.get('content.registeredMpacks');
-    
-    //TODO: mpacks - currently we create a service group for each mpack; this 
will be changed in the future
-    const serviceGroups = registeredMpacks.map(rmp => 
rmp.MpackInfo.mpack_name);
+  /**
+   * Shapes raw service group info into service group and service instance 
objects for use by the wizard.
+   */
+  loadServiceGroupsCallback: function (response) {
+    const serviceGroups = response.items.map(item =>
+      ({
+        name: item.service_group_name,
+        mpackVersionId: item.mpack_name + item.mpack_version
+      })
+    );
+    this.setDBProperty('serviceGroups', serviceGroups);
     this.set('content.serviceGroups', serviceGroups);
 
-    registeredMpacks.forEach(rmp => {
-      App.stackMapper.map(JSON.parse(JSON.stringify(rmp)));
-    });
+    const serviceInstances = response.items.reduce((serviceInstances, item) => 
+      serviceInstances.concat(item.services.map(service => 
+        ({
+          name: service.ServiceInfo.service_name,
+          serviceGroupName: service.ServiceInfo.service_group_name
+        })
+      ))
+    , []);
+    this.setDBProperty('serviceInstances', serviceInstances);
+    this.set('content.serviceInstances', serviceInstances);
+  },
+
+  loadServiceGroupsErrorCallback: function (jqXHR, ajaxOptions, error, opt) {
+    if (jqXHR.status === 404) {
+      //likely we are in the installer and no cluster was created yet,
+      //so act as if there were no error and we just got back an empty list of 
service groups
+      this.loadServiceGroupsCallback({ items: [] });
+    } else {
+      App.ajax.defaultErrorHandler(jqXHR, opt.url, opt.type, jqXHR.status);
+    }  
   },
 
   /**
+   * All service groups currently "in the cart."
+   * This includes the ones already existing in the cluster
+   * and any new ones added by the user in the current wizard.
+   */
+  allServiceGroups: function () {
+    return 
this.get('content.serviceGroups').concat(this.get('content.addedServiceGroups'));
+  }.property('content.serviceGroups.@each', 
'content.addedServiceGroups.@each'),
+
+  /**
    * Determine if <code>Assign Slaves and Clients</code> step ("step7") should 
be skipped
    * @method setSkipSlavesStep
    * @param services
diff --git a/ambari-web/app/controllers/wizard/selectMpacks_controller.js 
b/ambari-web/app/controllers/wizard/selectMpacks_controller.js
index f19057a..12e3f0f 100644
--- a/ambari-web/app/controllers/wizard/selectMpacks_controller.js
+++ b/ambari-web/app/controllers/wizard/selectMpacks_controller.js
@@ -20,6 +20,7 @@ var App = require('app');
 require('./wizardStep_controller');
 
 App.WizardSelectMpacksController = App.WizardStepController.extend({
+  //#region Properties
 
   name: 'wizardSelectMpacksController',
 
@@ -35,6 +36,87 @@ App.WizardSelectMpacksController = 
App.WizardStepController.extend({
 
   filterServicesPlaceholder: 
Em.I18n.t('installer.selectMpacks.filterServices'),
 
+  /** 
+   * Mpacks already registered on the server.
+   */
+  registeredMpacks: Em.computed.alias('content.registeredMpacks'),
+
+  /**
+   * Mpacks selected to be registered via the current wizard.
+   */
+  selectedMpacks: Em.computed.alias('content.selectedMpacks'),
+
+  /**
+   * Service instances already existin in the cluster.
+   */
+  serviceInstances: [],
+
+  /**
+   * Service instances added via the current wizard.
+   */
+  addedServiceInstances: [],
+  
+  /**
+   * All service groups currently "in the cart."
+   * This includes the ones already existing in the cluster
+   * and any new ones added by the user in the current wizard.
+   */
+  allServiceInstances: function () {
+    return 
this.get('serviceInstances').concat(this.get('addedServiceInstances'));
+  }.property('serviceInstances.@each', 'addedServiceInstances.@each'),
+
+  /**
+   * Service groups already existing in the cluster.
+   */
+  serviceGroups: [],
+
+  /**
+   * Service groups added via the current wizard.
+   */
+  addedServiceGroups: [],
+
+  /**
+   * All service groups currently "in the cart."
+   * This includes the ones already existing in the cluster
+   * and any new ones added by the user in the current wizard.
+   */
+  allServiceGroups: function () {
+    return this.get('serviceGroups').concat(this.get('addedServiceGroups'));
+  }.property('serviceGroups.@each', 'addedServiceGroups.@each'),
+
+  selectedUseCases: function selectedUseCases() {
+    return this.get('content.mpackUseCases').filterProperty('selected');
+  }.property('[email protected]'),
+
+  selectedServices: function selectedServices() {
+    const mpackServiceVersions = this.get('content.mpackServiceVersions');
+    return mpackServiceVersions ? mpackServiceVersions.filter(s => 
s.get('selected') === true) : [];
+  }.property('[email protected]'),
+
+  selectedMpackVersions: function selectedMpackVersions() {
+    const versions = this.get('content.mpackVersions');
+    return versions ? versions.filter(v => v.get('selected') === true) : [];
+  }.property('[email protected]', 'selectedServices'),
+
+  isSaved: function isSaved() {
+    const wizardController = this.get('wizardController');
+    if (wizardController) {
+      return wizardController.getStepSavedState('selectMpacks');
+    }
+    return false;
+  }.property('wizardController.content.stepsSavedState'),
+
+  isSubmitDisabled: function isSubmitDisabled() {
+    const mpackServiceVersions = this.get('content.mpackServiceVersions');
+    return App.get('router.btnClickInProgress')
+      || (this.get('wizardController.errors') && 
this.get('wizardController.errors').length > 0)
+      || mpackServiceVersions.filterProperty('selected', true).length === 0;
+  }.property('[email protected]', 
'App.router.btnClickInProgress', 'wizardController.errors'),
+
+  //#endregion
+
+  //#region Registry
+
   loadRegistry: function () {
     return App.ajax.send({
       name: 'registry.all',
@@ -44,10 +126,35 @@ App.WizardSelectMpacksController = 
App.WizardStepController.extend({
   },
 
   loadRegistrySucceeded: function (data) {
+    //Returns the first (newest) version of each mpack matching the list of 
names provided.
+    const getMpacksByName = mpackNames => {
+      const getMpackByName = mpackName => {
+        const mpacks = this.get('content.mpacks');
+        
+        if (mpacks) {
+          //reinstate this if/when the test runner can handle for..of loops
+          //for (let mpack of mpacks) {
+          //if (mpack.get('name') === mpackName) {
+          //  return mpack.get('versions')[0]; //TODO: mpacks - change this to 
the last item when sort order is fixed
+          //}
+          for (let i = 0, length = mpacks.length; i < length; i++) {      
+            if (mpacks[i].get('name') === mpackName) {
+              return mpacks[i].get('versions')[0]; //TODO: mpacks - change 
this to the last item when sort order is fixed
+            }
+          }
+        }  
+        
+        return null;
+      };
+    
+      return mpackNames.map(mpackName => getMpackByName(mpackName));
+    };
+
     const mpacks = data.items.reduce(
       (mpacks, registry) => mpacks.concat(
         registry.mpacks.map(mpack => {
           return Em.Object.create({
+            selected: function () { return 
this.versions.someProperty('selected', true); 
}.property('[email protected]'),
             name: mpack.RegistryMpackInfo.mpack_name,
             displayName: mpack.RegistryMpackInfo.mpack_display_name,
             description: mpack.RegistryMpackInfo.mpack_description,
@@ -146,61 +253,24 @@ App.WizardSelectMpacksController = 
App.WizardStepController.extend({
     this.set('content.mpackServiceVersions', mpackServiceVersions);
     this.set('content.mpackServices', mpackServices);
 
-    const usecases = data.items.reduce(
-      (usecases, registry) => usecases.concat(
-        registry.scenarios.map(usecase => {
+    const useCases = data.items.reduce(
+      (useCases, registry) => useCases.concat(
+        registry.scenarios.map(useCase => {
           return Em.Object.create({
             selected: false,
-            id: usecase.RegistryScenarioInfo.scenario_id || 
usecase.RegistryScenarioInfo.scenario_name, //TODO: mpacks - remove fallback 
when id is available
-            name: usecase.RegistryScenarioInfo.scenario_name,
-            displayName: usecase.RegistryScenarioInfo.scenario_display_name || 
usecase.RegistryScenarioInfo.scenario_name, //TODO: mpacks - remove fallback 
when display name is available
-            description: usecase.RegistryScenarioInfo.scenario_description,
-            mpacks: 
this.getMpacksByName(usecase.RegistryScenarioInfo.scenario_mpacks.map(mpack => 
mpack.name))
+            id: useCase.RegistryScenarioInfo.scenario_id || 
useCase.RegistryScenarioInfo.scenario_name, //TODO: mpacks - remove fallback 
when id is available
+            name: useCase.RegistryScenarioInfo.scenario_name,
+            displayName: useCase.RegistryScenarioInfo.scenario_display_name || 
useCase.RegistryScenarioInfo.scenario_name, //TODO: mpacks - remove fallback 
when display name is available
+            description: useCase.RegistryScenarioInfo.scenario_description,
+            mpacks: 
getMpacksByName(useCase.RegistryScenarioInfo.scenario_mpacks.map(mpack => 
mpack.name))
           });
         })
       ), []
     );
 
-    this.set('content.mpackUsecases', usecases);
+    this.set('content.mpackUseCases', useCases);
   },
   
-  getMpacksByName: function (mpackNames) {
-    return mpackNames.map(mpackName => this.getMpackByName(mpackName));
-  },
-
-  /**
-   * Returns the first (newest) version of the mpack with name matching 
mpackName.
-   * 
-   * @param {string} mpackName 
-   * @returns mpackVersion
-   */
-  getMpackByName: function (mpackName) {
-    const mpacks = this.get('content.mpacks');
-
-    if (mpacks) {
-      //reinstate this if/when the test runner can handle for..of loops
-      //for (let mpack of mpacks) {
-      //if (mpack.get('name') === mpackName) {
-      //  return mpack.get('versions')[0]; //TODO: mpacks - change this to the 
last item when sort order is fixed
-      //}
-      for (let i = 0, length = mpacks.length; i < length; i++) {      
-        if (mpacks[i].get('name') === mpackName) {
-          return mpacks[i].get('versions')[0]; //TODO: mpacks - change this to 
the last item when sort order is fixed
-        }
-      }
-    }
-    
-    return null;
-  },
-
-  isSaved: function () {
-    const wizardController = this.get('wizardController');
-    if (wizardController) {
-      return wizardController.getStepSavedState('selectMpacks');
-    }
-    return false;
-  }.property('wizardController.content.stepsSavedState'),
-
   loadRegistryFailed: function () {
     this.set('content.mpacks', []);
 
@@ -210,7 +280,7 @@ App.WizardSelectMpacksController = 
App.WizardStepController.extend({
     );
   },
 
-  registryLoaded() {
+  registryLoaded: function () {
     const mpacks = this.get('content.mpacks');
     const mpackVersions = this.get('content.mpackVersions');
     const mpackServices = this.get('content.mpackServices');
@@ -245,36 +315,9 @@ App.WizardSelectMpacksController = 
App.WizardStepController.extend({
     return deferred.promise();
   },
 
-  toggleMode: function () {
-    const isAdvancedMode = this.get('content.advancedMode');
-    
-    if (isAdvancedMode) { //toggling to Basic Mode
-      this.clearSelection();
-    } else { //toggling to Advanced Mode
-      this.set('noRecommendationAvailable', false);
-    }
-    
-    this.set('content.advancedMode', !isAdvancedMode);
-  },
+  //#endregion
 
-  loadStep: function () {
-    this.getRegistry().then(() => {
-      //add previously selected services
-      const selectedServices = this.get('content.selectedServices');
-      if (selectedServices) {
-        selectedServices.forEach(service => {
-          this.addService(service.id);
-        });
-      }
-    });
-  },
-
-  isSubmitDisabled: function () {
-    const mpackServiceVersions = this.get('content.mpackServiceVersions');
-    return App.get('router.btnClickInProgress')
-      || (this.get('wizardController.errors') && 
this.get('wizardController.errors').length > 0)
-      || mpackServiceVersions.filterProperty('selected', true).length === 0;
-  }.property('[email protected]', 
'App.router.btnClickInProgress', 'wizardController.errors'),
+  //#region Helpers
 
   getMpackVersionById: function (versionId) {
     const mpackVersions = this.get('content.mpackVersions');
@@ -300,82 +343,83 @@ App.WizardSelectMpacksController = 
App.WizardStepController.extend({
     return null;
   },
 
-  getUsecaseById: function (usecaseId) {
-    const usecases = this.get('content.mpackUsecases');
-    const byUsecaseId = usecase => usecase.id === usecaseId;
+  getUseCaseById: function (useCaseId) {
+    const useCases = this.get('content.mpackUseCases');
+    const byUseCaseId = useCase => useCase.id === useCaseId;
     
-    if (usecases) {
-      const usecase = usecases.find(byUsecaseId);
-      return usecase;
+    if (useCases) {
+      const useCase = useCases.find(byUseCaseId);
+      return useCase;
     }
     
     return null;
   },
 
-  displayMpackVersion: function (versionId) {
-    const version = this.getMpackVersionById(versionId);
+  getServiceGroup: function (serviceGroupName) {
+    return this.get('allServiceGroups').findProperty('name', serviceGroupName);
+  },
+
+  //#endregion
 
-    if (version) {
-      version.mpack.versions.forEach(mpackVersion => {
-        if (mpackVersion.get('id') === versionId) {
-          mpackVersion.set('displayed', true);
+  //#region Version Display
+
+  /**
+   * Changes which version of an mpack is displayed.
+   */
+  displayMpackVersion: function (mpackVersionId) {
+    const mpackVersion = this.getMpackVersionById(mpackVersionId);
+
+    if (mpackVersion) {
+      mpackVersion.get('mpack.versions').forEach(version => {
+        if (version.get('id') === mpackVersionId) {
+          version.set('displayed', true);
         } else {
-          mpackVersion.set('displayed', false);
+          version.set('displayed', false);
         }
       })
     }
   },
 
-  displayServiceVersion: function (versionId) {
-    const version = this.getServiceVersionById(versionId);
+  /**
+   * Changes which version of a service is displayed.
+   */
+  displayServiceVersion: function (serviceVersionId) {
+    const serviceVersion = this.getServiceVersionById(serviceVersionId);
 
-    if (version) {
-      version.service.versions.forEach(serviceVersion => {
-        if (serviceVersion.get('id') === versionId) {
-          serviceVersion.set('displayed', true);
+    if (serviceVersion) {
+      serviceVersion.get('service.versions').forEach(version => {
+        if (version.get('id') === serviceVersionId) {
+          version.set('displayed', true);
         } else {
-          serviceVersion.set('displayed', false);
+          version.set('displayed', false);
         }
       })
     }
   },
 
-  addMpackHandler: function (mpackVersionId) {
-    if (this.addMpack(mpackVersionId)) {
-      this.get('wizardController').setStepUnsaved('selectMpacks');
-    }
-  },
+  //#endregion
 
-  addMpack: function (mpackVersionId) {
-    const mpackVersion = this.getMpackVersionById(mpackVersionId);
+  //#region Use Cases
 
-    if (mpackVersion) {
-      mpackVersion.services.forEach(service => this.addService(service.id))
-      return true;
-    }
-
-    return false;
-  },
-
-  toggleUsecaseHandler: function (usecaseId) {
-    if (this.toggleUsecase(usecaseId)) {
+  toggleUseCaseHandler: function (useCaseId) {
+    if (this.toggleUseCase(useCaseId)) {
       this.get('wizardController').setStepUnsaved('selectMpacks');
     }
   },
 
-  toggleUsecase: function (usecaseId) {
+  toggleUseCase: function (useCaseId) {
     this.clearSelection();
     
-    const usecase = this.getUsecaseById(usecaseId);
-    if (usecase) {
-      const selected = usecase.get('selected');
-      usecase.set('selected', !selected);
+    const useCase = this.getUseCaseById(useCaseId);
+    if (useCase) {
+      const selected = useCase.get('selected');
+      useCase.set('selected', !selected);
       
-      const usecasesSelected = this.get('selectedUseCases');
-      if (usecasesSelected.length > 0) {
-        this.getUsecaseRecommendation()
-          .done(this.getUsecaseRecommendationSucceeded.bind(this))
-          .fail(this.getUsecaseRecommendationFailed.bind(this));
+      const useCasesSelected = this.get('selectedUseCases');
+      if (useCasesSelected.length > 0) {
+        this.getUseCaseRecommendation()
+          .done(this.getUseCaseRecommendationSucceeded.bind(this))
+          .fail(this.getUseCaseRecommendationFailed.bind(this));
       }
       
       return true;
@@ -384,25 +428,25 @@ App.WizardSelectMpacksController = 
App.WizardStepController.extend({
     return false;
   },
 
-  getUsecaseRecommendation: function (registryId) {
-    const usecases = 
this.get('content.mpackUsecases').filterProperty('selected').map(usecase =>
+  getUseCaseRecommendation: function (registryId) {
+    const useCases = 
this.get('content.mpackUseCases').filterProperty('selected').map(useCase =>
       ({
-        scenario_name: usecase.name
+        scenario_name: useCase.get('name')
       })
     );
 
     return App.ajax.send({
-      name: 'registry.recommendation.usecases',
+      name: 'registry.recommendation.useCases',
       data: {
         registryId: registryId || 1,
-        usecases: usecases
+        useCases: useCases
       },
       showLoadingPopup: true,
       sender: this
     });
   },
 
-  getUsecaseRecommendationSucceeded: function (data) {
+  getUseCaseRecommendationSucceeded: function (data) {
     this.clearSelection();
     
     let recommendations;
@@ -410,116 +454,296 @@ App.WizardSelectMpacksController = 
App.WizardStepController.extend({
       recommendations = data.resources[0].recommendations.mpack_bundles;
     }
     
-    if (recommendations && recommendations.length > 0
-      && recommendations[0].mpacks && recommendations[0].mpacks.length > 0) {
-      const mpackVersionIds = recommendations[0].mpacks.map(mpack => 
mpack.mpack_name + mpack.mpack_version);
-      mpackVersionIds.forEach(this.addMpack.bind(this));
+    if (recommendations && recommendations.length > 0 && 
recommendations[0].mpacks && recommendations[0].mpacks.length > 0) {
+      recommendations[0].mpacks.forEach(mpack => {
+        const serviceGroup = this.addServiceGroup(mpack.mpack_name + 
mpack.mpack_version, mpack.mpack_name);
+        serviceGroup.mpackVersion.services.forEach(service => 
this.addServiceInstance(service.name, service.name, serviceGroup));
+      });
     } else {
       this.set('noRecommendationAvailable', true);
     }
   },
 
-  getUsecaseRecommendationFailed: function () {
+  getUseCaseRecommendationFailed: function () {
     App.showAlertPopup(
       Em.I18n.t('common.error'), //header
       Em.I18n.t('installer.selectMpacks.getRecommendationFailed') //body
     );
   },
 
-  addServiceHandler: function (serviceId) {
-    if (this.addService(serviceId)) {
-      this.get('wizardController').setStepUnsaved('selectMpacks');
+  //#endregion
+
+  //#region Add/Remove
+
+  /**
+   * Adds mpack version to list of mpacks to register.
+   */
+  addMpackVersion: function (mpackVersionId) {
+    const mpackVersion = this.getMpackVersionById(mpackVersionId);
+    
+    if (mpackVersion) { 
+      mpackVersion.set('selected', true);
+      return mpackVersion;
     }
   },
 
-  addService: function (serviceId) {
-    const service = this.getServiceVersionById(serviceId);
+  /**
+   * Removes the specified mpack version from the list of mpacks to register.
+   * Sets all of its services as unselected.
+   */
+  removeMpackVersion: function (mpackVersion) {
+    mpackVersion.get('services').forEach(service => 
this.removeServiceVersion(service));
+    mpackVersion.set('selected', false);
+  },
 
-    if (service) {
-      service.set('selected', true);
-      service.set('mpackVersion.selected', true);
-      return true;
+  /**
+   * Set service version as selected.
+   */
+  addServiceVersion: function (serviceVersionId) {
+    const serviceVersion = this.getServiceVersionById(serviceVersionId);
+    
+    if (serviceVersion) { 
+      serviceVersion.set('selected', true);
+      return serviceVersion;
     }
+  },
 
-    return false;
+  /**
+   * Sets the specified service version as unselected.
+   */
+  removeServiceVersion: function (serviceVersion) {
+    serviceVersion.set('selected', false);
   },
 
-  removeServiceHandler: function (serviceId) {
-    if (this.removeService(serviceId)) {
-      this.get('wizardController').setStepUnsaved('selectMpacks');
+  /**
+   * Creates a service group object for use in this controller only.
+   */
+  createServiceGroup: function (name, mpackVersion, canRemove) {
+    const self = this;
+
+    return Em.Object.create({
+      name: name,
+      mpackVersion: mpackVersion,
+      serviceInstances: function () { return 
self.get('allServiceInstances').filterProperty('serviceGroup.name', name); 
}.property().volatile(),
+      canRemove: canRemove
+    });
+  },
+
+  /**
+   * Adds a service group bound to the mpack to the list of service groups to 
create.
+   */
+  addServiceGroup: function (mpackVersionId, serviceGroupName) {
+    const mpackVersion = this.addMpackVersion(mpackVersionId);
+
+    if (mpackVersion) {
+      if (!this.get('allServiceGroups').someProperty('name', 
serviceGroupName)) { //prevent duplicate service group names
+        const serviceGroup = this.createServiceGroup(serviceGroupName, 
mpackVersion, true);
+        this.get('addedServiceGroups').pushObject(serviceGroup);
+        return serviceGroup;
+      }
     }
   },
 
-  removeService: function (serviceId) {
-    const service = this.getServiceVersionById(serviceId);
+  /** 
+   * Removes a service group from the selection if it is removable.
+   * Removes all of its service instances.
+   * Returns true if service group was removed.
+   */
+  removeServiceGroup: function (serviceGroupName) {
+    const serviceGroup = this.getServiceGroup(serviceGroupName);
+    
+    if (serviceGroup && serviceGroup.get('canRemove')) {
+      serviceGroup.get('serviceInstances').forEach(serviceInstance => 
this.removeServiceInstance(serviceInstance.get('name'), serviceGroup));
 
-    if (service) {
-      service.set('selected', false);
-      service.set('mpackVersion.selected', 
service.get('mpackVersion.services').some(s => s.get('selected') === true));
+      const serviceGroups = this.get('addedServiceGroups');
+      const addedServiceGroups = serviceGroups.reject(serviceGroup => 
serviceGroup.get('name') === serviceGroupName);
+      this.set('addedServiceGroups', addedServiceGroups);
+      
       return true;
     }
+  },
 
-    return false;
+  /**
+   * Create service instance object for use in this controller only.
+   */
+  createServiceInstance: function (name, service, serviceGroup, canRemove) {
+    return Em.Object.create({
+      id: serviceGroup.get('name') + name,
+      name: name,
+      service: service,
+      serviceGroup: serviceGroup,
+      canRemove: canRemove
+    });
   },
 
-  removeMpackHandler: function (mpackId) {
-    if (this.removeMpack(mpackId)) {
-      this.get('wizardController').setStepUnsaved('selectMpacks');
+  /**
+   * Adds an instance of the specified service to the specified service group.
+   */
+  addServiceInstance: function (serviceName, serviceInstanceName, 
serviceGroup) {
+    const service = 
serviceGroup.get('mpackVersion.services').findProperty('name', serviceName);
+
+    if (service) {
+      if (!serviceGroup.get('serviceInstances').someProperty('name', 
serviceInstanceName)) { //prevent duplicate service instance names
+        const serviceInstance = 
this.createServiceInstance(serviceInstanceName, service, serviceGroup, true);
+        
+        //note that we do not add the serviceInstance directly to 
serviceGroup.serviceInstances
+        //because serviceGroup.serviceInstances is a FUNCTION that filters 
addedServiceInstances...
+        this.get('addedServiceInstances').pushObject(serviceInstance);
+        //...but we do need to notify explicitly because this property pulls 
data from another object
+        serviceGroup.notifyPropertyChange('serviceInstances'); 
+
+        this.addServiceVersion(service.get('id'));
+
+        return serviceInstance;
+      }  
     }
   },
 
-  removeMpack: function (mpackId) {
-    const mpackVersion = this.getMpackVersionById(mpackId);
+  /**
+   * Removes a service instance from a service group if it is removable.
+   * Returns true if service instance was removed.
+   */
+  removeServiceInstance: function (serviceInstanceName, serviceGroup) {
+    const serviceInstanceToRemove = 
serviceGroup.get('serviceInstances').findProperty('name', serviceInstanceName);
+
+    if (serviceInstanceToRemove && serviceInstanceToRemove.get('canRemove')) {
+      const addedServiceInstances = 
this.get('addedServiceInstances').reject(serviceInstance => 
serviceInstance.get('id') === serviceInstanceToRemove.get('id'));
+      this.set('addedServiceInstances', addedServiceInstances);
+      serviceGroup.notifyPropertyChange('serviceInstances'); //need to notify 
explicitly because this property pulls data from another object
 
-    if (mpackVersion) {
-      mpackVersion.get('services').forEach(service => 
this.removeService(service.get('id')));
       return true;
     }
-
-    return false;
   },
 
-  selectedUseCases: function () {
-    return this.get('content.mpackUsecases').filterProperty('selected');
-  }.property('[email protected]'),
+  //#endregion
 
-  selectedServices: function () {
-    const mpackServiceVersions = this.get('content.mpackServiceVersions');
-    return mpackServiceVersions ? mpackServiceVersions.filter(s => 
s.get('selected') === true) : [];
-  }.property('[email protected]'),
+  //#region Add/Remove Handlers
 
-  selectedMpackVersions: function () {
-    const versions = this.get('content.mpackVersions');
-    return versions ? versions.filter(v => v.get('selected') === true) : [];
-  }.property('[email protected]', 'selectedServices'),
+  /**
+   * Adds the mpack version to selection, creates a service group from the 
mpack version, 
+   * and adds instances of all services to the service group.
+   */
+  addMpackHandler: function (mpackVersionId) {
+    const mpackVersion = this.addMpackVersion(mpackVersionId);
+    
+    if (mpackVersion) {
+      const serviceGroup = this.addServiceGroup(mpackVersionId, 
mpackVersion.get('mpack.name')); //TODO: for now we are setting the service 
group name equal to the mpack name
+      
+      if (serviceGroup) {
+        mpackVersion.get('services').forEach(service => 
this.addServiceInstance(service.get('name'), service.get('name'), 
serviceGroup)); //TODO: for now we are setting the service instance name equal 
to the service name
+      }
+    
+      this.get('wizardController').setStepUnsaved('selectMpacks');
+    }
+  },
 
-  hasSelectedMpackVersions: function () {
-    const versions = this.get('content.mpackVersions');
-    return versions ? versions.some(v => v.get('selected') === true) : false;
-  }.property('[email protected]', 'selectedServices'),
+  /**
+   * Removes the service group corresponding to the specified mpack version
+   * and removes the mpack version from the selection.
+   */
+  removeMpackHandler: function (mpackVersionId) {
+    const mpackVersion = this.getMpackVersionById(mpackVersionId);
+    
+    if (mpackVersion) {
+      const serviceGroupName = mpackVersion.get('mpack.name'); //TODO: later 
this will come from the UI
+      
+      if (this.removeServiceGroup(serviceGroupName)) {
+        this.removeMpackVersion(mpackVersion);
+  
+        this.get('wizardController').setStepUnsaved('selectMpacks');
+      }
+    }  
+  },
 
-  clearSelection: function () {
-    const mpackServiceVersions = this.get('content.mpackServiceVersions');
-    if (mpackServiceVersions) {
-      mpackServiceVersions.setEach('selected', false);
-    }
+  /**
+   * Adds an instance of a service.
+   * Finds the service group to add it to by matching the mpack version id.
+   * If the service group does not exist, it is created first.
+   */
+  addServiceHandler: function (serviceId) {
+    const service = this.addServiceVersion(serviceId);
     
-    const versions = this.get('content.mpackVersions');
-    if (versions) {
-      versions.setEach('selected', false);
+    if (service) {
+      //add mpack containing the service to list of mpacks be registered
+      const mpackVersion = service.get('mpackVersion');
+      this.addMpackVersion(mpackVersion.get('id'));
+
+      //find or create service group for the mpack containing the service
+      const serviceGroupName = mpackVersion.get('mpack.name'); //TODO: later 
this will come from the UI
+      const serviceGroup = this.getServiceGroup(serviceGroupName) || 
this.addServiceGroup(mpackVersion.get('id'), serviceGroupName);
+      
+      if (serviceGroup) {
+        //add a service instance to the service group
+        const serviceInstanceName = service.get('name'); //TODO: later this 
will come from the UI
+        this.addServiceInstance(service.get('name'), serviceInstanceName, 
serviceGroup);
+      }
+      
+      this.get('wizardController').setStepUnsaved('selectMpacks');
     }
+  },
+
+  /**
+   * Removes the service instance corresponding to the service specified.
+   * Deselects the service.
+   */
+  removeServiceHandler: function (serviceVersionId) {
+    const serviceVersion = this.getServiceVersionById(serviceVersionId);
     
-    if (this.get('content.advancedMode')) {
-      const usecases = this.get('content.mpackUsecases');
-      if (usecases) {
-        usecases.setEach('selected', false);
+    if (serviceVersion) {
+      const serviceInstanceName = serviceVersion.get('name'); //TODO: later 
this will come from the UI
+      const mpackVersionId = serviceVersion.get('mpackVersion.id');
+      const serviceGroupName = serviceVersion.get('mpackVersion.mpack.name'); 
//TODO: later this will come from the UI
+      
+      const serviceGroup = this.getServiceGroup(serviceGroupName);
+      if (serviceGroup) {
+        if (this.removeServiceInstance(serviceInstanceName, serviceGroup)) {
+          this.removeServiceVersion(serviceVersion);
+
+          if (serviceGroup.get('serviceInstances').length === 0) {
+            this.removeMpackHandler(mpackVersionId);
+          }
+
+          this.get('wizardController').setStepUnsaved('selectMpacks');
+        }
       }
     }  
+  },
 
-    this.set('noRecommendationAvailable', false);
-    this.get('wizardController').setStepUnsaved('selectMpacks');
+  /**
+   * Removes a service group and deselects its associated mpack.
+   * 
+   * Note: This is just a thin wrapper for removeMpackHandler()
+   */
+  removeServiceGroupHandler: function (serviceGroupName) {
+    const serviceGroup = this.getServiceGroup(serviceGroupName);
+
+    if (serviceGroup) {
+      this.removeMpackHandler(serviceGroup.get('mpackVersion.id'));
+    }
+  },
+
+  /**
+   * Removes the service instance specified.
+   * Deselects the corresponding service.
+   * 
+   * Note: This is just a thin wrapper for removeServiceHandler()
+   */
+  removeServiceInstanceHandler: function (serviceInstanceId) {
+    const allServiceGroups = this.get('allServiceGroups');
+    
+    allServiceGroups.forEach(serviceGroup => {
+      const serviceInstance = 
serviceGroup.get('serviceInstances').findProperty('id', serviceInstanceId);
+      
+      if (serviceInstance) {
+        this.removeServiceHandler(serviceInstance.get('service.id'));
+      }
+    })
   },
 
+  //#endregion
+
+  //#region Filtering
+
   filteredMpacks: function () {
     const mpacks = this.get('content.mpacks');
     const filterText = this.get('filterMpacksText').toLowerCase();
@@ -558,6 +782,119 @@ App.WizardSelectMpacksController = 
App.WizardStepController.extend({
     this.set('filterServicesText', "");
   },
 
+  //#endregion
+
+  //#region Load/Save
+
+  loadStep: function () {
+    this.getRegistry().done(() => {
+      this.getServiceGroups();
+      this.getServiceInstances();
+    });
+  },
+
+  /**
+   * Shapes service group info into objects for use in this controller.
+   */
+  getServiceGroups: function () {
+    const serviceGroups = this.get('content.serviceGroups');
+    if (serviceGroups) {
+      serviceGroups.forEach(serviceGroup => {
+        const mpackVersion = 
this.getMpackVersionById(serviceGroup.mpackVersionId);
+        if (mpackVersion) {
+          const sg = this.createServiceGroup(serviceGroup.name, mpackVersion, 
false);
+          this.get('serviceGroups').pushObject(sg);
+        }
+      });
+    }
+
+    const addedServiceGroups = this.get('content.addedServiceGroups');
+    if (addedServiceGroups) {
+      addedServiceGroups.forEach(serviceGroup => {
+        this.addServiceGroup(serviceGroup.mpackVersionId, serviceGroup.name);
+      });
+    }
+  },
+
+  /**
+   * Shapes service instance info into objects for use in this controller.
+   */
+  getServiceInstances: function () {
+    const serviceInstances = this.get('content.serviceInstances');
+    if (serviceInstances) {
+      serviceInstances.forEach(serviceInstance => {
+        const serviceGroup = 
this.getServiceGroup(serviceInstance.serviceGroupName);
+      
+        if (serviceGroup) {
+          const service = 
serviceGroup.get('mpackVersion.services').findProperty('name', 
serviceInstance.serviceName);
+        
+          if (service) {
+            this.get('serviceInstances').pushObject({
+              id: serviceInstance.serviceGroupName + serviceInstance.name,
+              name: serviceInstance.name,
+              service: service,
+              serviceGroup: serviceGroup,
+              canRemove: false
+            });
+          }
+        }
+      });
+    }
+
+    const addedServiceInstance = this.get('content.addedServiceInstances');
+    if (addedServiceInstance) {
+      addedServiceInstance.forEach(serviceInstance => {
+        const serviceGroup = 
this.getServiceGroup(serviceInstance.serviceGroupName);
+      
+        if (serviceGroup) {
+          this.addServiceInstance(serviceInstance.serviceName, 
serviceInstance.name, serviceGroup);
+        }
+      });
+    }
+  },
+
+  toggleMode: function () {
+    const isAdvancedMode = this.get('content.advancedMode');
+    
+    if (isAdvancedMode) { //toggling to Basic Mode
+      this.clearSelection();
+    } else { //toggling to Advanced Mode
+      this.set('noRecommendationAvailable', false);
+    }
+    
+    this.set('content.advancedMode', !isAdvancedMode);
+  },
+
+  clearSelection: function () {
+    const content = this.get('content');
+
+    if (content) {
+      const mpackServiceVersions = content.get('mpackServiceVersions');
+      if (mpackServiceVersions) {
+        mpackServiceVersions.setEach('selected', false);
+      }
+    
+      const versions = content.get('mpackVersions');
+      if (versions) {
+        versions.setEach('selected', false);
+      }
+    
+      if (content.get('advancedMode')) {
+        const useCases = content.get('mpackUseCases');
+        if (useCases) {
+          useCases.setEach('selected', false);
+        }
+      }
+    }
+
+    this.set('addedServiceGroups', []);
+    this.set('addedServiceInstances', []);
+
+    this.set('noRecommendationAvailable', false);
+
+    this.get('wizardController').setStepUnsaved('selectMpacks');
+  },
+
   /**
    * Onclick handler for <code>Next</code> button.
    * Disable 'Next' button while it is already under process. (using Router's 
property 'nextBtnClickInProgress')
@@ -569,38 +906,54 @@ App.WizardSelectMpacksController = 
App.WizardStepController.extend({
     }
 
     if (!this.get('isSubmitDisabled')) {
-      const selectedServices = this.get('selectedServices').map(service =>
+      const addedServiceGroups = 
this.get('addedServiceGroups').map(serviceGroup =>
         ({
-          id: service.id,
-          name: service.name,
-          mpackName: service.mpackVersion.mpack.name,
-          mpackVersion: service.mpackVersion.version
+          name: serviceGroup.get('name'),
+          mpackName: serviceGroup.get('mpackVersion.mpack.name'),
+          mpackVersion: serviceGroup.get('mpackVersion.version')
         })
       );
-      this.set('content.selectedServices', selectedServices);
+      this.set('content.addedServiceGroups', addedServiceGroups);
 
-      const selectedServiceNames = selectedServices.map(service => 
service.name);
+      const addedServiceInstances = 
this.get('addedServiceInstances').map(serviceInstance =>
+        ({
+          id: serviceInstance.get('service.id'),
+          name: serviceInstance.get('name'),
+          serviceGroupName: serviceInstance.get('serviceGroup.name'),
+          serviceName: serviceInstance.get('service.name'),
+          mpackName: serviceInstance.get('service.mpackVersion.mpack.name'),
+          mpackVersion: serviceInstance.get('service.mpackVersion.version')
+        })
+      )
+      this.set('content.addedServiceInstances', addedServiceInstances);
+
+      //content.selectedServices is populated for legacy code; may be able to 
remove later
+      this.set('content.selectedServices', addedServiceInstances);
+      //content.selectedServiceNames is populated for legacy code; may be able 
to remove later
+      const selectedServiceNames = addedServiceInstances.map(serviceInstance 
=> serviceInstance.name);
       this.set('content.selectedServiceNames', selectedServiceNames);
 
       const selectedMpacks = 
this.get('selectedMpackVersions').map(mpackVersion => {
         const selectedMpack = {
-          id: `${mpackVersion.mpack.name}-${mpackVersion.version}`,
-          name: mpackVersion.mpack.name,
-          version: mpackVersion.version,
-          displayName: mpackVersion.mpack.displayName,
-          publicUrl: mpackVersion.mpackUrl,
-          downloadUrl: mpackVersion.mpackUrl,
-          registryId: mpackVersion.mpack.registryId
+          id: 
`${mpackVersion.get('mpack.name')}-${mpackVersion.get('version')}`,
+          name: mpackVersion.get('mpack.name'),
+          version: mpackVersion.get('version'),
+          displayName: mpackVersion.get('mpack.displayName'),
+          publicUrl: mpackVersion.get('mpackUrl'),
+          downloadUrl: mpackVersion.get('mpackUrl'),
+          registryId: mpackVersion.get('mpack.registryId')
         };
         
+        //update selected mpack version with previously customized repo URLs 
for same mpack version
+        //this will need to be done when the user has returned to this step
+        //after previously going forward, customizing the URLs, and then going 
back to mpack selection
         const oldSelectedMpacks = this.get('content.selectedMpacks');
-        let oldSelectedMpack;
         if (oldSelectedMpacks) {
-          oldSelectedMpack = oldSelectedMpacks.find(mpack => mpack.name === 
mpackVersion.mpack.name && mpack.version === mpackVersion.version);
-        }
-        if (oldSelectedMpack) {
-          selectedMpack.downloadUrl = oldSelectedMpack.downloadUrl;
-          selectedMpack.operatingSystems = oldSelectedMpack.operatingSystems;
+          const oldSelectedMpack = oldSelectedMpacks.find(mpack => mpack.name 
=== mpackVersion.get('mpack.name') && mpack.version === 
mpackVersion.get('version'));
+          if (oldSelectedMpack) {
+            selectedMpack.downloadUrl = oldSelectedMpack.downloadUrl;
+            selectedMpack.operatingSystems = oldSelectedMpack.operatingSystems;
+          }
         }
         
         return selectedMpack;
@@ -610,4 +963,6 @@ App.WizardSelectMpacksController = 
App.WizardStepController.extend({
       App.router.send('next');
     }
   }
+
+  //#endregion
 });
diff --git a/ambari-web/app/messages.js b/ambari-web/app/messages.js
index 2cb1191..ef87eec 100644
--- a/ambari-web/app/messages.js
+++ b/ambari-web/app/messages.js
@@ -675,7 +675,7 @@ Em.I18n.translations = {
   'installer.selectMpacks.body.header.services': 'Services',
   'installer.selectMpacks.body.selected.header': 'Selected Management Packs',
   'installer.selectMpacks.loadRegistryFailed': 'Could not load available 
management packs. The software registry may not be available.',
-  'installer.selectMpacks.noUsecasesAvailable': 'No use cases are available.',
+  'installer.selectMpacks.noUseCasesAvailable': 'No use cases are available.',
   'installer.selectMpacks.noMpacksAvailable': 'No management packs are 
available.',
   'installer.selectMpacks.noServicesAvailable': 'No services are available.',
   'installer.selectMpacks.noMpacksSelected': 'No management packs selected.',
@@ -1115,15 +1115,14 @@ Em.I18n.translations = {
   'installer.step7.preInstallChecks.notRunChecksWarnPopup.primary':'Ignore and 
Proceed',
   'installer.step7.preInstallChecks.notRunChecksWarnPopup.secondary':'Run Pre 
Install Checks',
   'installer.step7.preInstallChecks.checksPopup.header':'Pre Install Checks',
-
-
   'installer.step7.assign.master.body':'Assign <strong>{0}</strong> to {1} you 
want to run {2} on.',
   'installer.step7.assign.master.dependent.component.body':'If not present {0} 
will also be installed on the selected host. ',
   'installer.step7.missing.service.header':'Missing Service',
   'installer.step7.missing.service.body': '{0} service should be added to the 
cluster to {1}.',
-  'assign.master.popup.header':'Select {0} host',
-  'assign.master.popup.cancel.body':'Not selecting {0} host will disable 
interactive query.',
 
+  'assign.master.popup.header': 'Select {0} host',
+  'assign.master.popup.cancel.body':'Not selecting {0} host will disable 
interactive query.',
+  'assign.master.no.servicegroups': 'No management packs found.', //TODO - 
mpacks: replace "management packs" with "service groups" in the future
 
   'installer.step8.header': 'Review',
   'installer.step8.body': 'Please review the configuration before 
installation',
diff --git a/ambari-web/app/mixins/wizard/assign_master_components.js 
b/ambari-web/app/mixins/wizard/assign_master_components.js
index 518959c..aa78259 100644
--- a/ambari-web/app/mixins/wizard/assign_master_components.js
+++ b/ambari-web/app/mixins/wizard/assign_master_components.js
@@ -32,6 +32,13 @@ var validationUtils = require('utils/validator');
  */
 App.AssignMasterComponents = Em.Mixin.create(App.HostComponentValidationMixin, 
App.HostComponentRecommendationMixin, {
 
+  serviceGroups: Em.computed.alias('wizardController.allServiceGroups'),
+
+  hasServiceGroups: function () {
+    const serviceGroups = this.get('serviceGroups');
+    return serviceGroups && serviceGroups.length > 0;
+  }.property('serviceGroups'),
+
   /**
    * Array of master component names to show on the page
    * By default is empty, this means that masters of all selected services 
should be shown
@@ -174,7 +181,7 @@ App.AssignMasterComponents = 
Em.Mixin.create(App.HostComponentValidationMixin, A
    * Check if <code>installerWizard</code> used
    * @type {bool}
    */
-  isInstallerWizard: Em.computed.equal('content.controllerName', 
'installerController'),
+  isInstaller: Em.computed.equal('content.controllerName', 
'installerController'),
 
   /**
    * Master components which could be assigned to multiple hosts
@@ -465,7 +472,7 @@ App.AssignMasterComponents = 
Em.Mixin.create(App.HostComponentValidationMixin, A
    */
   getRecommendationRequestData: function(options) {
     var res = this._super(options);
-    if (!this.get('isInstallerWizard')) {
+    if (!this.get('isInstaller')) {
       res.data.recommendations = this.getCurrentMasterSlaveBlueprint();
     }
     return res;
@@ -492,7 +499,8 @@ App.AssignMasterComponents = 
Em.Mixin.create(App.HostComponentValidationMixin, A
       isRecommendationsLoaded: false,
       backFromNextStep: false,
       selectedServicesMasters: [],
-      servicesMasters: []
+      servicesMasters: [],
+      generalErrorMessages: []
     });
     App.StackServiceComponent.find().forEach(function (stackComponent) {
       stackComponent.set('serviceComponentId', 1);
@@ -520,12 +528,16 @@ App.AssignMasterComponents = 
Em.Mixin.create(App.HostComponentValidationMixin, A
         self.set('backFromNextStep', true);
       }
       
-      self.getRecommendedHosts({
-        hosts: self.getHosts(),
-        mpack_instances: self.get('wizardController').getMpackInstances()
-      }).then(function () {
-        self.loadStepCallback(self.createComponentInstallationObjects(), self);
-      });
+      if (self.get('hasServiceGroups')) {
+        self.getRecommendedHosts({
+          hosts: self.getHosts(),
+          mpack_instances: self.get('wizardController').getMpackInstances()
+        }).then(function () {
+          self.loadStepCallback(self.createComponentInstallationObjects(), 
self);
+        });
+      } else {
+        
self.get('generalErrorMessages').pushObject(Em.I18n.t('assign.master.no.servicegroups'));
+      }
     });
   },
 
@@ -592,12 +604,11 @@ App.AssignMasterComponents = 
Em.Mixin.create(App.HostComponentValidationMixin, A
    */
   renderHostInfo: function () {
     var self = this;
-    var isInstaller = (this.get('wizardController.name') === 
'installerController' || this.get('content.controllerName') === 
'installerController');
     return App.ajax.send({
-      name: isInstaller ? 'hosts.info.install' : 
'hosts.high_availability.wizard',
+      name: this.get('isInstaller') ? 'hosts.info.install' : 
'hosts.high_availability.wizard',
       sender: this,
       data: {
-        hostNames: isInstaller ? this.getHosts().join() : null
+        hostNames: this.get('isInstaller') ? this.getHosts().join() : null
       }
     }).success(function(data) {
       self.loadWizardHostsSuccessCallback(data)
@@ -680,7 +691,7 @@ App.AssignMasterComponents = 
Em.Mixin.create(App.HostComponentValidationMixin, A
 
     App.StackServiceComponent.find().forEach(function(component) {
       var isMasterCreateOnConfig = 
this.get('mastersToCreate').contains(component.get('componentName'));
-      if (this.get('isInstallerWizard') && 
(component.get('isShownOnInstallerAssignMasterPage') || isMasterCreateOnConfig) 
) {
+      if (this.get('isInstaller') && 
(component.get('isShownOnInstallerAssignMasterPage') || isMasterCreateOnConfig) 
) {
         stackMasterComponentsMap[component.get('componentName')] = component;
       } else if (component.get('isShownOnAddServiceAssignMasterPage') || 
this.get('mastersToShow').contains(component.get('componentName')) || 
isMasterCreateOnConfig) {
         stackMasterComponentsMap[component.get('componentName')] = component;
@@ -1200,26 +1211,28 @@ App.AssignMasterComponents = 
Em.Mixin.create(App.HostComponentValidationMixin, A
 
     this.set('validationInProgress', true);
     
-    this.getRecommendedHosts({
-      hosts: hostNames,
-      mpack_instances: mpackInstances,
-      components: this.getCurrentComponentHostMap()
-    }).done(function() {
-      self.validateSelectedHostComponents({
+    if (this.get('hasServiceGroups')) {
+      this.getRecommendedHosts({
         hosts: hostNames,
         mpack_instances: mpackInstances,
-        blueprint: self.get('recommendations')
+        components: this.getCurrentComponentHostMap()
       }).always(function() {
-        if (callback) {
-          callback();
-        }
-        self.set('validationInProgress', false);
-        if (self.get('runQueuedValidation')) {
-          self.set('runQueuedValidation', false);
-          self.recommendAndValidate(callback);
-        }
-      });
-    });
+        self.validateSelectedHostComponents({
+          hosts: hostNames,
+          mpack_instances: mpackInstances,
+          blueprint: self.get('recommendations')
+        }).then(function() {
+          if (callback) {
+            callback();
+          }
+          self.set('validationInProgress', false);
+          if (self.get('runQueuedValidation')) {
+            self.set('runQueuedValidation', false);
+            self.recommendAndValidate(callback);
+          }
+        });
+      }, true);
+    }
   },
 
   getCurrentComponentHostMap: function() {
@@ -1254,7 +1267,7 @@ App.AssignMasterComponents = 
Em.Mixin.create(App.HostComponentValidationMixin, A
     }
   },
 
-  nextButtonDisabled: Em.computed.or('App.router.btnClickInProgress', 
'submitDisabled', 'validationInProgress', '!isLoaded'),
+  nextButtonDisabled: Em.computed.or('App.router.btnClickInProgress', 
'submitDisabled', 'validationInProgress', '!isLoaded', '!hasServiceGroups'),
 
   /**
    * Submit button click handler
@@ -1274,8 +1287,7 @@ App.AssignMasterComponents = 
Em.Mixin.create(App.HostComponentValidationMixin, A
         self.recommendAndValidate(function () {
           
self.showValidationIssuesAcceptBox(self._goNextStepIfValid.bind(self));
         });
-      }
-      else {
+      } else {
         this.updateIsSubmitDisabled();
         this._goNextStepIfValid();
         this.set('submitButtonClicked', false);
diff --git a/ambari-web/app/models/stack_service.js 
b/ambari-web/app/models/stack_service.js
index bf04681..53429bf 100644
--- a/ambari-web/app/models/stack_service.js
+++ b/ambari-web/app/models/stack_service.js
@@ -31,7 +31,7 @@ var Dependency = Ember.Object.extend({
   }),
 
   compatibleServices: function(services) {
-    return services.filterProperty('serviceName', this.get('name'));
+    return services.filter(item => item === this.get('name'));
   },
 
   isMissing: function(selectedServices) {
@@ -140,7 +140,7 @@ App.StackService = DS.Model.extend({
 
   dependencies: function(availableServices) {
     var result = [];
-    this.get('requiredServices').forEach(function(serviceName) {
+    this.get('requiredServices').forEach(function (serviceName) {
       var service = availableServices.findProperty('serviceName', serviceName);
       if (service) {
         result.push(Dependency.fromService(service));
@@ -152,7 +152,7 @@ App.StackService = DS.Model.extend({
   /**
    * Add dependencies which are not already included in selectedServices to 
the given missingDependencies collection
   */
-  collectMissingDependencies: function(selectedServices, availableServices, 
missingDependencies) {
+  collectMissingDependencies: function (selectedServices, availableServices, 
missingDependencies) {
     this._missingDependencies(selectedServices, 
availableServices).forEach(function (dependency) {
       this._addMissingDependency(dependency, availableServices, 
missingDependencies);
     }.bind(this));
diff --git a/ambari-web/app/routes/installer.js 
b/ambari-web/app/routes/installer.js
index 4666e9f..8280212 100644
--- a/ambari-web/app/routes/installer.js
+++ b/ambari-web/app/routes/installer.js
@@ -303,6 +303,10 @@ module.exports = Em.Route.extend(App.RouterRedirections, {
         controller.save('selectedServiceNames');
         controller.save('selectedServices');
         controller.save('selectedMpacks');
+        controller.save('serviceGroups');
+        controller.save('addedServiceGroups');
+        controller.save('serviceInstances');
+        controller.save('addedServiceInstances');
         controller.save('advancedMode');
         var wizardSelectMpacksController = 
router.get('wizardSelectMpacksController');
         // Clear subsequent settings if user changed service selections
@@ -415,7 +419,6 @@ module.exports = Em.Route.extend(App.RouterRedirections, {
       App.set('router.nextBtnClickInProgress', true);
       const controller = router.get('installerController');
       controller.save('registeredMpacks');
-      controller.save('serviceGroups');
       controller.save('selectedStack');
       const downloadConfig = controller.get('content.downloadConfig');
       if (downloadConfig && downloadConfig.useCustomRepo) {
diff --git a/ambari-web/app/styles/wizard.less 
b/ambari-web/app/styles/wizard.less
index 3bafff5..dc9f0b4 100644
--- a/ambari-web/app/styles/wizard.less
+++ b/ambari-web/app/styles/wizard.less
@@ -783,9 +783,12 @@
     background-color: #ddd;
     margin-bottom: 5px;
     outline: none;
-    &:hover {
+    &:hover:not(.disabled) {
       background-color: #ccc;
     }
+    &.disabled {
+      cursor: not-allowed;
+    }
   }
 
   #registry {
@@ -831,6 +834,9 @@
       border-style: solid;
       border-radius: 50%;
       outline: none;
+      &.disabled {
+        border: initial;
+      }
       &.checked:after {
         content: '\f00c';
       }
diff --git a/ambari-web/app/templates/wizard/selectMpacks.hbs 
b/ambari-web/app/templates/wizard/selectMpacks.hbs
index 0c60a1e..5ca727a 100644
--- a/ambari-web/app/templates/wizard/selectMpacks.hbs
+++ b/ambari-web/app/templates/wizard/selectMpacks.hbs
@@ -18,84 +18,84 @@
 <div id="select-mpacks" class="wizard-content col-md-9">
   <h4 class="step-title">{{t installer.selectMpacks.body.header}}</h4>
   {{#if isSaved}}
-  <div class="alert alert-warning" role="alert"><strong>{{t 
common.warning}}:</strong> {{t installer.warning.changes}}</div>
+    <div class="alert alert-warning" role="alert"><strong>{{t 
common.warning}}:</strong> {{t installer.warning.changes}}</div>
   {{/if}}
   <div class="display-flex">
     <!-- Registry -->
     <div id="registry" class="panel panel-default col-md-8 display-flex 
direction-col">
       <div class="panel-heading display-flex align-center">
         <div id="useCaseHeader">
-        {{#if controller.content.advancedMode}}
-        <ul class="nav nav-tabs" role="tablist">
-          <li role="presentation" class="active">
-            <a href="#mpacks" aria-controls="mpacks" role="tab" 
data-toggle="tab">{{t installer.selectMpacks.body.header.mpacks}}</a>
-          </li>
-          <li role="presentation">
-            <a href="#services" aria-controls="services" role="tab" 
data-toggle="tab">{{t installer.selectMpacks.body.header.services}}</a>
-          </li>
-        </ul>
-        {{else}}
-        <span class="step-description">{{t 
installer.selectMpacks.body.header.useCases}}</span>
-        {{/if}}
+          {{#if controller.content.advancedMode}}
+            <ul class="nav nav-tabs" role="tablist">
+              <li role="presentation" class="active">
+                <a href="#mpacks" aria-controls="mpacks" role="tab" 
data-toggle="tab">{{t installer.selectMpacks.body.header.mpacks}}</a>
+              </li>
+              <li role="presentation">
+                <a href="#services" aria-controls="services" role="tab" 
data-toggle="tab">{{t installer.selectMpacks.body.header.services}}</a>
+              </li>
+            </ul>
+          {{else}}
+            <span class="step-description">{{t 
installer.selectMpacks.body.header.useCases}}</span>
+          {{/if}}
         </div>
         <button id="modeButton" class="btn btn-secondary pull-right" {{action 
toggleMode target="view"}}>
           {{#if controller.content.advancedMode}}
-          {{t installer.selectMpacks.basicMode}}
+            {{t installer.selectMpacks.basicMode}}
           {{else}}
-          {{t installer.selectMpacks.advancedMode}}
+            {{t installer.selectMpacks.advancedMode}}
           {{/if}}
         </button>
         {{#if controller.content.advancedMode}}
-        <span class="more-info" data-toggle="tooltip" data-placement="bottom" 
{{translateAttr title="installer.selectMpacks.advancedModeHelp" }}></span>      
  
+          <span class="more-info" data-toggle="tooltip" 
data-placement="bottom" {{translateAttr 
title="installer.selectMpacks.advancedModeHelp"}}></span>        
         {{else}}
-        <span class="more-info" data-toggle="tooltip" data-placement="bottom" 
{{translateAttr title="installer.selectMpacks.basicModeHelp"}}></span>
+          <span class="more-info" data-toggle="tooltip" 
data-placement="bottom" {{translateAttr 
title="installer.selectMpacks.basicModeHelp"}}></span>
         {{/if}}
       </div>
       <div class="panel-body tab-content flex-fill">
         {{#if controller.content.advancedMode}}
-        <div role="tabpanel" class="tab-pane active" id="mpacks">
-          <div class="input-group filter-input">
-            {{view Em.TextField class="form-control" 
valueBinding="controller.filterMpacksText" 
placeholderBinding="controller.filterMpacksPlaceholder"}}
-            <span class="input-group-btn">
-              <button class="btn btn-default icon-button-addon" type="button" 
{{action clearFilterMpacks target="controller"}}><span class="icon 
icon-rotate-left"></span></button>
-            </span>
-          </div>
-          <div class="options-list">
-            {{#if controller.filteredMpacks}}
-            {{#each mpack in controller.filteredMpacks}}
-            {{view App.WizardMpackView mpackBinding="mpack"}}
-            {{/each}}
-            {{else}}
-            <div class="no-data">{{t 
installer.selectMpacks.noMpacksAvailable}}</div>
-            {{/if}}
+          <div role="tabpanel" class="tab-pane active" id="mpacks">
+            <div class="input-group filter-input">
+              {{view Em.TextField class="form-control" 
valueBinding="controller.filterMpacksText" 
placeholderBinding="controller.filterMpacksPlaceholder"}}
+              <span class="input-group-btn">
+                <button class="btn btn-default icon-button-addon" 
type="button" {{action clearFilterMpacks target="controller"}}><span 
class="icon icon-rotate-left"></span></button>
+              </span>
+            </div>
+            <div class="options-list">
+              {{#if controller.filteredMpacks}}
+                {{#each mpack in controller.filteredMpacks}}
+                  {{view App.WizardMpackView mpackBinding="mpack"}}
+                {{/each}}
+              {{else}}
+                <div class="no-data">{{t 
installer.selectMpacks.noMpacksAvailable}}</div>
+              {{/if}}
+            </div>
           </div>
-        </div>
-        <div role="tabpanel" class="tab-pane" id="services">
-          <div class="input-group filter-input">
-            {{view Em.TextField class="form-control" 
valueBinding="controller.filterServicesText" 
placeholderBinding="controller.filterServicesPlaceholder"}}
-            <span class="input-group-btn">
-              <button class="btn btn-default icon-button-addon" type="button" 
{{action clearFilterServices target="controller"}}><span class="icon 
icon-rotate-left"></span></button>
-            </span>
-          </div>
-          <div class="options-list">
-            {{#if controller.filteredServices}} 
-            {{#each service in controller.filteredServices}}
-            {{view App.WizardServiceView serviceBinding="service"}}
-            {{/each}}
-            {{else}}
-            <div class="no-data">{{t 
installer.selectMpacks.noServicesAvailable}}</div>
-            {{/if}}
+          <div role="tabpanel" class="tab-pane" id="services">
+            <div class="input-group filter-input">
+              {{view Em.TextField class="form-control" 
valueBinding="controller.filterServicesText" 
placeholderBinding="controller.filterServicesPlaceholder"}}
+              <span class="input-group-btn">
+                <button class="btn btn-default icon-button-addon" 
type="button" {{action clearFilterServices target="controller"}}><span 
class="icon icon-rotate-left"></span></button>
+              </span>
+            </div>
+            <div class="options-list">
+              {{#if controller.filteredServices}} 
+                {{#each service in controller.filteredServices}}
+                  {{view App.WizardServiceView serviceBinding="service"}}
+                {{/each}}
+              {{else}}
+                <div class="no-data">{{t 
installer.selectMpacks.noServicesAvailable}}</div>
+              {{/if}}
+            </div>
           </div>
-        </div>
         {{else}}
-        <div role="tabpanel" class="tab-pane active" id="usecases">
+        <div role="tabpanel" class="tab-pane active" id="useCases">
           <div class="options-list">
-            {{#if controller.content.mpackUsecases}} 
-            {{#each usecase in controller.content.mpackUsecases}}
-            {{view App.WizardUsecaseView usecaseBinding="usecase"}}
-            {{/each}}
+            {{#if controller.content.mpackUseCases}} 
+              {{#each useCase in controller.content.mpackUseCases}}
+                {{view App.WizardUseCaseView useCaseBinding="useCase"}}
+              {{/each}}
             {{else}}
-            <div class="no-data">{{t 
installer.selectMpacks.noUsecasesAvailable}}</div>
+              <div class="no-data">{{t 
installer.selectMpacks.noUseCasesAvailable}}</div>
             {{/if}}
           </div>
         </div>
@@ -106,16 +106,16 @@
     <div class="panel panel-default col-md-4 selected-list display-flex 
direction-col">
       <div class="panel-heading">{{t 
installer.selectMpacks.body.selected.header}}&nbsp;({{controller.selectedMpackVersions.length}})</div>
       <div class="panel-body flex-fill">
-        {{#if controller.hasSelectedMpackVersions}}
-        {{#each mpackVersion in controller.selectedMpackVersions}}
-          {{view App.WizardSelectedMpackVersionView 
mpackVersionBinding="mpackVersion"}}
-        {{/each}}
-        {{else}}
-        {{#if controller.noRecommendationAvailable}}
-        <div class="no-data">{{t 
installer.selectMpacks.noRecommendationAvailable}}</div>
+        {{#if view.hasServiceGroups}}
+          {{#each serviceGroup in view.serviceGroups}}
+            {{view App.WizardServiceGroupView 
serviceGroupBinding="serviceGroup"}}
+          {{/each}}
         {{else}}
-        <div class="no-data">{{t 
installer.selectMpacks.noMpacksSelected}}</div>
-        {{/if}}
+          {{#if controller.noRecommendationAvailable}}
+            <div class="no-data">{{t 
installer.selectMpacks.noRecommendationAvailable}}</div>
+          {{else}}
+            <div class="no-data">{{t 
installer.selectMpacks.noMpacksSelected}}</div>
+          {{/if}}
         {{/if}}
       </div>
     </div>
diff --git a/ambari-web/app/templates/wizard/selectMpacks/mpack.hbs 
b/ambari-web/app/templates/wizard/selectMpacks/mpack.hbs
index caae17e..0dfbd5c 100644
--- a/ambari-web/app/templates/wizard/selectMpacks/mpack.hbs
+++ b/ambari-web/app/templates/wizard/selectMpacks/mpack.hbs
@@ -32,7 +32,7 @@
   <div>
   {{#each service in view.services}}
     {{#if service.selected}}
-    <button class="capsule capsule-selected">
+    <button class="capsule capsule-selected disabled">
       {{service.name}}&nbsp;{{service.version}}&nbsp;<i class="icon 
icon-ok"></i>
     </button>
     {{else}}
@@ -42,5 +42,9 @@
     {{/if}}
   {{/each}}
   </div>
-  <button class="btn btn-default option-add-button" {{action addMpack 
target="view"}}></button>
+  {{#if mpack.selected}}
+    <button class="btn btn-default option-add-button checked 
disabled"></button>
+  {{else}}
+    <button class="btn btn-default option-add-button" {{action addMpack 
target="view"}}></button>
+  {{/if}}
 </div>
diff --git a/ambari-web/app/templates/wizard/selectMpacks/service.hbs 
b/ambari-web/app/templates/wizard/selectMpacks/service.hbs
index 402ac46..cdbf34a 100644
--- a/ambari-web/app/templates/wizard/selectMpacks/service.hbs
+++ b/ambari-web/app/templates/wizard/selectMpacks/service.hbs
@@ -21,7 +21,7 @@
       {{service.displayName}} 
       <select {{action changeVersion on="change" target="view"}}>
         {{#each ver in service.versions}}
-        <option {{bindAttr value="ver.id" 
selected="ver.displayed"}}>{{ver.version}}</option>
+          <option {{bindAttr value="ver.id" 
selected="ver.displayed"}}>{{ver.version}}</option>
         {{/each}}
       </select>
     </p>
@@ -29,8 +29,8 @@
   </div>
   <div class="option-description">{{service.description}}</div>
   {{#if service.displayedVersion.selected}}
-  <button class="btn btn-default option-add-button checked"></button>
+    <button class="btn btn-default option-add-button checked 
disabled"></button>
   {{else}}
-  <button class="btn btn-default option-add-button" {{action add 
target="view"}}></button>
+    <button class="btn btn-default option-add-button" {{action add 
target="view"}}></button>
   {{/if}}
 </div>
diff --git 
a/ambari-web/app/templates/wizard/selectMpacks/selectedMpackVersion.hbs 
b/ambari-web/app/templates/wizard/selectMpacks/serviceGroup.hbs
similarity index 53%
rename from 
ambari-web/app/templates/wizard/selectMpacks/selectedMpackVersion.hbs
rename to ambari-web/app/templates/wizard/selectMpacks/serviceGroup.hbs
index 39aa63c..3ae3036 100644
--- a/ambari-web/app/templates/wizard/selectMpacks/selectedMpackVersion.hbs
+++ b/ambari-web/app/templates/wizard/selectMpacks/serviceGroup.hbs
@@ -17,24 +17,30 @@
 }}
 <div class="option">
   <div class="option-title">
-    <p>{{mpackVersion.mpack.displayName}} {{mpackVersion.version}}</p>
+    <p>{{serviceGroup.name}} {{serviceGroup.mpackVersion.version}}</p>
   </div>
   {{#if controller.content.advancedMode}}
-  <button class="option-remove-button" {{action removeMpack mpackVersion.id 
target="view" }}></button>
+    {{#if serviceGroup.canRemove}}
+      <button class="option-remove-button" {{action removeServiceGroup 
serviceGroup.name target="view"}}></button>
+    {{/if}}
   {{/if}}
   <div class="option-body">
-  {{#each service in mpackVersion.services}}
-    {{#if service.selected}}
+    {{#each serviceInstance in serviceGroup.serviceInstances}}
       {{#if controller.content.advancedMode}}
-      <button class="capsule capsule-selected" {{action removeService 
service.id target="view"}}>
-        {{service.name}}&nbsp;{{service.version}}&nbsp;<i class="icon 
icon-remove"></i>
-      </button>
+        {{#if serviceInstance.canRemove}}
+          <button class="capsule capsule-selected" {{action 
removeServiceInstance serviceInstance.id target="view"}}>
+            {{serviceInstance.name}}&nbsp;{{serviceInstance.version}}&nbsp;<i 
class="icon icon-remove"></i>
+          </button>
+        {{else}}
+          <button class="capsule capsule-selected">
+            {{serviceInstance.name}}&nbsp;{{serviceInstance.version}}
+          </button>
+        {{/if}}
       {{else}}
-      <button class="capsule capsule-selected">
-        {{service.name}}&nbsp;{{service.version}}
-      </button>
+        <button class="capsule capsule-selected">
+          {{serviceInstance.name}}&nbsp;{{serviceInstance.version}}
+        </button>
       {{/if}}
-    {{/if}}
-  {{/each}}
+    {{/each}}
   </div>
-</div>
+</div>
\ No newline at end of file
diff --git a/ambari-web/app/templates/wizard/selectMpacks/usecase.hbs 
b/ambari-web/app/templates/wizard/selectMpacks/useCase.hbs
similarity index 71%
rename from ambari-web/app/templates/wizard/selectMpacks/usecase.hbs
rename to ambari-web/app/templates/wizard/selectMpacks/useCase.hbs
index 5a94d8c..5c783e3 100644
--- a/ambari-web/app/templates/wizard/selectMpacks/usecase.hbs
+++ b/ambari-web/app/templates/wizard/selectMpacks/useCase.hbs
@@ -17,12 +17,12 @@
 }}
 <div class="option">
   <div class="option-title">
-    {{usecase.displayName}}&nbsp;<span class="glyphicon 
glyphicon-question-sign"></span>
+    {{useCase.displayName}}&nbsp;<span class="glyphicon 
glyphicon-question-sign"></span>
   </div>
-  <div class="option-description">{{usecase.description}}</div>
-  {{#if usecase.selected}}
-  <button class="btn btn-default option-add-button checked" {{action toggle 
target="view"}}></button>
+  <div class="option-description">{{useCase.description}}</div>
+  {{#if useCase.selected}}
+    <button class="btn btn-default option-add-button checked" {{action toggle 
target="view"}}></button>
   {{else}}
-  <button class="btn btn-default option-add-button" {{action toggle 
target="view"}}></button>
+    <button class="btn btn-default option-add-button" {{action toggle 
target="view"}}></button>
   {{/if}}
 </div>
diff --git a/ambari-web/app/utils/ajax/ajax.js 
b/ambari-web/app/utils/ajax/ajax.js
index dd3b977..3ba696f 100644
--- a/ambari-web/app/utils/ajax/ajax.js
+++ b/ambari-web/app/utils/ajax/ajax.js
@@ -3201,14 +3201,14 @@ var urls = {
     real: '/registries/{registryId}/mpacks/{name}/versions/{version}',
   },
 
-  'registry.recommendation.usecases': {
+  'registry.recommendation.useCases': {
     real: '/registries/{registryId}/recommendations',
     format: function (data) {
       return {
         type: 'POST',
         data: JSON.stringify({
           recommend: "scenario-mpacks",
-          selected_scenarios: data.usecases
+          selected_scenarios: data.useCases
         })
       };
     }
@@ -3222,7 +3222,15 @@ var urls = {
         url: 'http://' + data.hsiHost + ':' + data.port + '/leader'
       }
     }
-  }
+  },
+
+  'servicegroup.get_all': {
+    'real': '/clusters/{clusterName}/servicegroups'
+  },
+
+  'servicegroup.get_all_details': {
+    'real': '/clusters/{clusterName}/servicegroups?fields=*'
+  },
 };
 /**
  * Replace data-placeholders to its values
diff --git a/ambari-web/app/views/wizard/selectMpacks_view.js 
b/ambari-web/app/views/wizard/selectMpacks_view.js
index 80d2cf6..0b69ea3 100644
--- a/ambari-web/app/views/wizard/selectMpacks_view.js
+++ b/ambari-web/app/views/wizard/selectMpacks_view.js
@@ -21,6 +21,12 @@ var App = require('app');
 App.WizardSelectMpacksView = Em.View.extend({
   templateName: require('templates/wizard/selectMpacks'),
 
+  serviceGroups: Em.computed.alias('controller.allServiceGroups'),
+
+  hasServiceGroups: function () {
+    return this.get('serviceGroups').length > 0;
+  }.property('serviceGroups.@each'),
+
   didInsertElement: function () {
     this.get('controller').loadStep();
 
@@ -35,8 +41,8 @@ App.WizardSelectMpacksView = Em.View.extend({
   },
 
   toggleMode: function () {
-    const isAdvancedMode = this.get('controller.content.advancedMode');
     const controller = this.get('controller');
+    const isAdvancedMode = controller.get('content.advancedMode');
     const toggleMode = controller.toggleMode.bind(controller);
 
     if (isAdvancedMode) { //toggling to Basic (Use Cases) Mode
@@ -94,14 +100,14 @@ App.WizardSelectMpacksView = Em.View.extend({
 /**
  * View for each use case in the registry
  */
-App.WizardUsecaseView = Em.View.extend({
-  templateName: require('templates/wizard/selectMpacks/usecase'),
+App.WizardUseCaseView = Em.View.extend({
+  templateName: require('templates/wizard/selectMpacks/useCase'),
 
   /**
    * Handle add/remove button clicked
    */
   toggle: function () {
-    this.get('controller').toggleUsecaseHandler(this.get('usecase.id'));
+    this.get('controller').toggleUseCaseHandler(this.get('useCase.id'));
   }
 });
 
@@ -167,28 +173,28 @@ App.WizardServiceView = Em.View.extend({
 });
 
 /**
- * View for each selected mpack
+ * View for each service group in the cart.
  */
-App.WizardSelectedMpackVersionView = Em.View.extend({
-  templateName: require('templates/wizard/selectMpacks/selectedMpackVersion'),
+App.WizardServiceGroupView = Em.View.extend({
+  templateName: require('templates/wizard/selectMpacks/serviceGroup'),
 
   /**
-   * Handle remove service button clicked.
+   * Handle remove service instance button clicked.
    *
    * @param  {type} event
    */
-  removeService: function (event) {
-    const serviceId = event.context;
-    this.get('controller').removeServiceHandler(serviceId);
+  removeServiceInstance: function (event) {
+    const serviceInstanceId = event.context;
+    this.get('controller').removeServiceInstanceHandler(serviceInstanceId);
   },
 
   /**
-   * Handle remove mpack button clicked.
+   * Handle remove service group button clicked.
    * 
    * @param {any} event 
    */
-  removeMpack: function (event) {
-    const mpackId = event.context;
-    this.get('controller').removeMpackHandler(mpackId);
+  removeServiceGroup: function (event) {
+    const serviceGroupName = event.context;
+    this.get('controller').removeServiceGroupHandler(serviceGroupName);
   }
 });
diff --git a/ambari-web/test/controllers/installer_test.js 
b/ambari-web/test/controllers/installer_test.js
index a9586b8..628073e 100644
--- a/ambari-web/test/controllers/installer_test.js
+++ b/ambari-web/test/controllers/installer_test.js
@@ -556,8 +556,6 @@ describe('App.InstallerController', function () {
         sinon.stub(App.stackMapper, 'map');
         installerController.loadRegisteredMpacks();
         expect(App.stackMapper.map.calledThrice).to.be.true;
-        const serviceGroups = installerController.get('content.serviceGroups');
-        expect(serviceGroups).to.deep.equal(["mpack1", "mpack2", "mpack3"]);
         installerController.getDBProperty.restore();
         App.stackMapper.map.restore();
       });  
diff --git a/ambari-web/test/controllers/main/service/add_controller_test.js 
b/ambari-web/test/controllers/main/service/add_controller_test.js
index 6315891..0616a96 100644
--- a/ambari-web/test/controllers/main/service/add_controller_test.js
+++ b/ambari-web/test/controllers/main/service/add_controller_test.js
@@ -238,24 +238,6 @@ describe('App.AddServiceController', function() {
 
   });
 
-  describe('#loadHostsErrorCallback', function () {
-
-    beforeEach(function () {
-      sinon.stub(App.ajax, 'defaultErrorHandler', Em.K);
-    });
-
-    afterEach(function () {
-      App.ajax.defaultErrorHandler.restore();
-    });
-
-    it('should execute default error handler', function () {
-      addServiceController.loadHostsErrorCallback({status: '500'}, 
'textStatus', 'errorThrown', {url: 'url', type: 'GET'});
-      expect(App.ajax.defaultErrorHandler.calledOnce).to.be.true;
-      expect(App.ajax.defaultErrorHandler.calledWith({status: '500'}, 'url', 
'GET', '500')).to.be.true;
-    });
-
-  });
-
   describe('#loadServices', function() {
     var mock = {
       db: {}
diff --git a/ambari-web/test/controllers/wizard/selectMpacks_test.js 
b/ambari-web/test/controllers/wizard/selectMpacks_test.js
index fc3188e..ee6420e 100644
--- a/ambari-web/test/controllers/wizard/selectMpacks_test.js
+++ b/ambari-web/test/controllers/wizard/selectMpacks_test.js
@@ -474,11 +474,11 @@ describe('App.WizardSelectMpacksController', function () {
       isSubmitDisabled: false,
       selectedServices: null,
       selectedMpackVersions: null,
-      content: {
+      content: Em.Object.create({
         selectedServices: null,
         selectedServiceNames: null,
         selectedMpacks: null
-      },
+      }),
       wizardController: wizardController      
     });
 
@@ -493,38 +493,20 @@ describe('App.WizardSelectMpacksController', function () {
 
   describe('#loadStep', function () {
     it('adds previously selected services to selection', function () {
-      wizardSelectMpacksController.set('content.selectedServices', [
-        { id: "HDPCORE1.0.0-b85ZOOKEEPER" },
-        { id: "HDPCORE1.0.0-b85HDFS" }
-      ]);
+      // wizardSelectMpacksController.set('content.selectedServices', [
+      //   { id: "HDPCORE1.0.0-b85ZOOKEEPER" },
+      //   { id: "HDPCORE1.0.0-b85HDFS" }
+      // ]);
 
-      wizardSelectMpacksController.loadStep();
+      // wizardSelectMpacksController.loadStep();
       
-      var service = 
wizardSelectMpacksController.getServiceVersionById("HDPCORE1.0.0-b85ZOOKEEPER");
-      expect(service.get('selected')).to.be.true;
-      expect(service.get('mpackVersion.selected')).to.be.true;
+      // var service = 
wizardSelectMpacksController.getServiceVersionById("HDPCORE1.0.0-b85ZOOKEEPER");
+      // expect(service.get('selected')).to.be.true;
+      // expect(service.get('mpackVersion.selected')).to.be.true;
 
-      var service = 
wizardSelectMpacksController.getServiceVersionById("HDPCORE1.0.0-b85HDFS");
-      expect(service.get('selected')).to.be.true;
-      expect(service.get('mpackVersion.selected')).to.be.true;
-    });
-  });
-
-  describe('#getMpacksByName', function () {
-    it('should return an array of mpacks matching the given names', function 
() {
-      //this test assumes that mpackNames contains the names of all mpacks in 
the test registry data at the top of this file
-      var mpackNames = [
-        'HDPCORE',
-        'ODS'
-      ]
-
-      var expected = wizardSelectMpacksController.get('content.mpacks');
-      var actual = wizardSelectMpacksController.getMpacksByName(mpackNames);
-      
-      expect(actual.length).to.equal(expected.length);
-      for (var i = 0, length = actual.length; i < length; i++) {
-        expect(actual[i].get('mpack.name')).to.equal(expected[i].get('name'));
-      }
+      // var service = 
wizardSelectMpacksController.getServiceVersionById("HDPCORE1.0.0-b85HDFS");
+      // expect(service.get('selected')).to.be.true;
+      // expect(service.get('mpackVersion.selected')).to.be.true;
     });
   });
 
@@ -542,17 +524,17 @@ describe('App.WizardSelectMpacksController', function () {
     });
   });
 
-  describe('#getUsecaseById', function () {
+  describe('#getUseCaseById', function () {
     it('should return the correct use case', function () {
-      var usecases = [
+      var useCases = [
         Em.Object.create({ id: 0 }),
         Em.Object.create({ id: 1 }),
         Em.Object.create({ id: 2 }),
       ];
-      wizardSelectMpacksController.set('content.mpackUsecases', usecases);
+      wizardSelectMpacksController.set('content.mpackUseCases', useCases);
 
-      var actual = wizardSelectMpacksController.getUsecaseById(1);
-      expect(actual).to.equal(usecases[1]);
+      var actual = wizardSelectMpacksController.getUseCaseById(1);
+      expect(actual).to.equal(useCases[1]);
     });
   });
 
@@ -571,7 +553,7 @@ describe('App.WizardSelectMpacksController', function () {
   });
 
   describe('#displayMpackVersion', function () {
-    var actual = {
+    var actual = Em.Object.create({
       mpack: {
         versions: [
           Em.Object.create({ id: "1", displayed: true }),
@@ -580,14 +562,14 @@ describe('App.WizardSelectMpacksController', function () {
           Em.Object.create({ id: "4", displayed: false }),
         ]
       }
-    };
+    });
 
     before(function () {
       sinon.stub(wizardSelectMpacksController, 
'getMpackVersionById').returns(actual);
     });
 
     it('should set chosen mpack version to displayed and set others to not 
displayed', function () {
-      var expected = {
+      var expected = Em.Object.create({
         mpack: {
           versions: [
             Em.Object.create({ id: "1", displayed: false }),
@@ -596,7 +578,7 @@ describe('App.WizardSelectMpacksController', function () {
             Em.Object.create({ id: "4", displayed: false }),
           ]
         }
-      };
+      });
       
       wizardSelectMpacksController.displayMpackVersion("3");
       expect(actual).to.deep.equal(expected);
@@ -608,7 +590,7 @@ describe('App.WizardSelectMpacksController', function () {
   });
 
   describe('#displayServiceVersion', function () {
-    var actual = {
+    var actual = Em.Object.create({
       service: {
         versions: [
           Em.Object.create({ id: "1", displayed: true }),
@@ -617,14 +599,14 @@ describe('App.WizardSelectMpacksController', function () {
           Em.Object.create({ id: "4", displayed: false }),
         ]
       }
-    };
+    });
 
     before(function () {
       sinon.stub(wizardSelectMpacksController, 
'getServiceVersionById').returns(actual);
     });
 
     it('should set chosen service version to displayed and set others to not 
displayed', function () {
-      var expected = {
+      var expected = Em.Object.create({
         service: {
           versions: [
             Em.Object.create({ id: "1", displayed: false }),
@@ -633,7 +615,7 @@ describe('App.WizardSelectMpacksController', function () {
             Em.Object.create({ id: "4", displayed: false }),
           ]
         }
-      };
+      });
 
       wizardSelectMpacksController.displayServiceVersion("3");
       expect(actual).to.deep.equal(expected);
@@ -644,285 +626,512 @@ describe('App.WizardSelectMpacksController', function 
() {
     });
   });
 
-  describe('#addServiceHandler', function () {
-    var actual;
+  describe('#addMpackVersion', function () {
+    it('should set mpack version as selected', function () {
+      var actual = Em.Object.create({
+        selected: false
+      });
 
-    before(function () {
-      var initial = Em.Object.create({
-        "0": true,
-        "1": true,
-        "2": true,
-        "3": true,
-        "4": true
-      })
-      
wizardSelectMpacksController.set('wizardController.content.stepsSavedState', 
initial);
-      
-      actual = Em.Object.create({
-        selected: false,
-        mpackVersion: {
-          selected: false,
-          services: [
-            actual,
-            Em.Object.create({ selected: false })
-          ]
-        }
+      var expected = Em.Object.create({
+        selected: true
       });
 
-      sinon.stub(wizardSelectMpacksController, 
'getServiceVersionById').returns(actual);
-    });
-    
-    after(function () {
-      wizardSelectMpacksController.getServiceVersionById.restore();
+      sinon.stub(wizardSelectMpacksController, 
'getMpackVersionById').returns(actual);
+      wizardSelectMpacksController.addMpackVersion("anyId");
+      wizardSelectMpacksController.getMpackVersionById.restore();
+
+      expect(actual).to.deep.equal(expected);
     });
+  });
 
-    it('should set the service and its mpack to selected and set the step to 
unsaved', function () {
-      var expected = Em.Object.create({
+  describe('#removeMpackVersion', function () {
+    it('should set mpack version as not selected and deselect all of its 
services', function () {
+      var actual = Em.Object.create({
         selected: true,
-        mpackVersion: {
-          selected: true,
-          services: [
-            expected,
-            Em.Object.create({ selected: false })
-          ]
-        }
+        services: [
+          Em.Object.create({ selected: true }),
+          Em.Object.create({ selected: true })
+        ]
       });
-      
-      wizardSelectMpacksController.addServiceHandler("HDPCore3.0.0ZOOKEEPER");
-      expect(actual).to.deep.equal(expected);
-      
-      var final = Em.Object.create({
-        "0": true,
-        "1": false,
-        "2": true,
-        "3": true,
-        "4": true
+
+      var expected = Em.Object.create({
+        selected: false,
+        services: [
+          Em.Object.create({ selected: false }),
+          Em.Object.create({ selected: false })
+        ]
       });
-      var savedState = 
wizardSelectMpacksController.get('wizardController.content.stepsSavedState');
-      expect(savedState).to.deep.equal(final);
+
+      wizardSelectMpacksController.removeMpackVersion(actual);
+
+      expect(actual).to.deep.equal(expected);
     });
   });
 
-  describe('#removeServiceHandler', function () {
-    beforeEach(function () {
-      var initial = Em.Object.create({
-        "0": true,
-        "1": true,
-        "2": true,
-        "3": true,
-        "4": true
-      })
-      
wizardSelectMpacksController.set('wizardController.content.stepsSavedState', 
initial);
-    });
+  describe('#addServiceVersion', function () {
+    it('should set the service version as selected', function () {
+      var actual = Em.Object.create({
+        selected: false
+      });
 
-    afterEach(function () {
-      var final = Em.Object.create({
-        "0": true,
-        "1": false,
-        "2": true,
-        "3": true,
-        "4": true
+      var expected = Em.Object.create({
+        selected: true
       });
-      var savedState = 
wizardSelectMpacksController.get('wizardController.content.stepsSavedState');
-      expect(savedState).to.deep.equal(final);
 
+      sinon.stub(wizardSelectMpacksController, 
'getServiceVersionById').returns(actual);
+      wizardSelectMpacksController.addServiceVersion();
       wizardSelectMpacksController.getServiceVersionById.restore();
+
+      expect(actual).to.deep.equal(expected);
     });
+  });
 
-    it('should set only the service to not selected and set the step to 
unsaved', function () {
+  describe('#removeServiceVersion', function () {
+    it('should set the service version as selected', function () {
       var actual = Em.Object.create({
-        selected: true,
-        mpackVersion: {
-          selected: true,
-          services: []
-        }
+        selected: true
       });
-      actual.set('mpackVersion.services', [
-        actual,
-        Em.Object.create({ selected: true })
-      ]);
 
-      sinon.stub(wizardSelectMpacksController, 
'getServiceVersionById').returns(actual);
+      var expected = Em.Object.create({
+        selected: false
+      });
 
-      
wizardSelectMpacksController.removeServiceHandler("HDPCore3.0.0ZOOKEEPER");
-      expect(actual.get('selected')).to.be.false;
-      expect(actual.get('mpackVersion.selected')).to.be.true;
+      wizardSelectMpacksController.removeServiceVersion(actual);
+
+      expect(actual).to.deep.equal(expected);
     });
+  });
 
-    it('when removing the last service, it should set the service and its 
mpack to not selected and set the step to unsaved', function () {
-      var actual = Em.Object.create({
-        selected: true,
-        mpackVersion: {
-          selected: true,
-          services: []
-        }
+  describe('#createServiceGroup', function () {
+    it('should construct a service group object', function () {
+      var name = 'name';
+      var mpackVersion = 'mpackVersion';
+      var canRemove = true;
+
+      var expected = Em.Object.create({
+        name: name,
+        mpackVersion: mpackVersion,
+        canRemove: canRemove
       });
-      actual.set('mpackVersion.services', [
-        actual,
-        Em.Object.create({ selected: false })
-      ]);
 
-      sinon.stub(wizardSelectMpacksController, 
'getServiceVersionById').returns(actual);
+      var actual = wizardSelectMpacksController.createServiceGroup(name, 
mpackVersion, canRemove);
+      delete actual.serviceInstances; //this property is a function with 
complicated dependencies so we can't really mock it up for the comparison
 
-      
wizardSelectMpacksController.removeServiceHandler("HDPCore3.0.0ZOOKEEPER");
-      expect(actual.get('selected')).to.be.false;
-      expect(actual.get('mpackVersion.selected')).to.be.false;
+      expect(actual).to.deep.equal(expected);
     });
   });
 
-  describe('#addMpackHandler', function () {
-    var actual;
-
-    before(function () {
-      var initial = Em.Object.create({
-        "0": true,
-        "1": true,
-        "2": true,
-        "3": true,
-        "4": true
+  describe('#addServiceGroup', function () {
+    it('should add a service group to addedServiceGroups', function () {
+      var mpackVersion = Em.Object.create({
+        name: 'service',
+        selected: true
+      });
+      sinon.stub(wizardSelectMpacksController, 
'addMpackVersion').returns(mpackVersion);
+      
+      var serviceGroupName = 'serviceGroup';
+      var serviceGroup = Em.Object.create({
+        name: serviceGroupName,
+        mpackVersion: mpackVersion,
+        canRemove: true
       })
-      
wizardSelectMpacksController.set('wizardController.content.stepsSavedState', 
initial);
+      sinon.stub(wizardSelectMpacksController, 
'createServiceGroup').returns(serviceGroup);
 
-      var service0 = Em.Object.create({
-        id: 0,
-        selected: false
-      });
+      var addedServiceGroups = 
wizardSelectMpacksController.get('addedServiceGroups');
+      var countBefore = addedServiceGroups.length;
 
-      var service1 = Em.Object.create({
-        id: 1,
-        selected: false
-      });
+      wizardSelectMpacksController.addServiceGroup('mpackVersionId', 
serviceGroupName);
 
-      actual = Em.Object.create({
-        selected: false,
-        services: [
-          service0,
-          service1
-        ]
+      wizardSelectMpacksController.addMpackVersion.restore();
+      wizardSelectMpacksController.createServiceGroup.restore();
+
+      expect(addedServiceGroups.length).to.equal(countBefore + 1);
+      expect(addedServiceGroups[countBefore]).to.equal(serviceGroup) //last 
object pushed should be serviceGroup
+    });
+
+    it('should not add a duplicate service group to addedServiceGroups', 
function () {
+      var mpackVersion = Em.Object.create({
+        name: 'service',
+        selected: true
       });
+      sinon.stub(wizardSelectMpacksController, 
'addMpackVersion').returns(mpackVersion);
+      
+      var serviceGroupName = 'serviceGroupDupe';
+      var expected = Em.Object.create({
+        name: serviceGroupName,
+        mpackVersion: mpackVersion,
+        canRemove: true
+      })
+      sinon.stub(wizardSelectMpacksController, 
'createServiceGroup').returns(expected);
+      //prime with the duplicate
+      wizardSelectMpacksController.addServiceGroup('mpackVersionId', 
serviceGroupName);
 
-      actual.get('services')[0].set('mpackVersion', actual);
-      actual.get('services')[1].set('mpackVersion', actual);
+      var addedServiceGroups = 
wizardSelectMpacksController.get('addedServiceGroups');
+      var countBefore = addedServiceGroups.length;
 
-      sinon.stub(wizardSelectMpacksController, 
'getMpackVersionById').returns(actual);
-      sinon.stub(wizardSelectMpacksController, 'getServiceVersionById')
-        .withArgs(0).returns(service0)
-        .withArgs(1).returns(service1);
+      //now try adding the same one again
+      wizardSelectMpacksController.addServiceGroup('mpackVersionId', 
serviceGroupName);
+
+      wizardSelectMpacksController.addMpackVersion.restore();
+      wizardSelectMpacksController.createServiceGroup.restore();
+
+      expect(addedServiceGroups.length).to.equal(countBefore); //count should 
not change
     });
+  });
 
-    after(function () {
-      wizardSelectMpacksController.getMpackVersionById.restore();
-      wizardSelectMpacksController.getServiceVersionById.restore();
+  describe('#removeServiceGroup', function () {
+    it('should remove the service group from addedServiceGroups', function () {
+      var serviceGroup = Em.Object.create({
+        name: 'sg1',
+        canRemove: true,
+        serviceInstances: []
+      });
+
+      wizardSelectMpacksController.set('addedServiceGroups', [
+        serviceGroup,  
+        Em.Object.create({
+          name: 'sg2'
+        })
+      ]);
+
+      wizardSelectMpacksController.removeServiceGroup('sg1');
+      
expect(wizardSelectMpacksController.get('addedServiceGroups').length).to.equal(1);
     });
+  });
+
+  describe('#createServiceInstance', function () {
+    it('should construct a service instance object', function () {
+      var name = 'name';
+      var serviceGroup = Em.Object.create({ name: 'sg1' });
+      var service = 'service';
+      var canRemove = true;
 
-    it('should set the mpack and all of its services to selected and set the 
step to unsaved', function () {
       var expected = Em.Object.create({
-        selected: true,
-        services: [
-          Em.Object.create({
-            id: 0,
-            selected: true
-          }),
-          Em.Object.create({
-            id: 1,
-            selected: true
-          })
-        ]
+        id: serviceGroup.get('name') + name,
+        name: name,
+        service: service,
+        serviceGroup: serviceGroup,
+        canRemove: canRemove
       });
-      expected.get('services')[0].set('mpackVersion', expected);
-      expected.get('services')[1].set('mpackVersion', expected);
 
-      wizardSelectMpacksController.addMpackHandler("HDPCore3.0.0");
-      expect(actual).to.deep.equal(expected);
+      var actual = wizardSelectMpacksController.createServiceInstance(name, 
service, serviceGroup, canRemove);
 
-      var final = Em.Object.create({
-        "0": true,
-        "1": false,
-        "2": true,
-        "3": true,
-        "4": true
-      });
-      var savedState = 
wizardSelectMpacksController.get('wizardController.content.stepsSavedState');
-      expect(savedState).to.deep.equal(final);
+      expect(actual).to.deep.equal(expected);
     });
   });
 
-  describe('#removeMpackHandler', function () {
-    before(function () {
-      var initial = Em.Object.create({
-        "0": true,
-        "1": true,
-        "2": true,
-        "3": true,
-        "4": true
-      })
-      
wizardSelectMpacksController.set('wizardController.content.stepsSavedState', 
initial);
+  describe('#addServiceInstance', function () {
+    it('should add a service instance to addedServiceInstances', function () {
+      var serviceName = 'service';
+      var serviceGroup = Em.Object.create({
+        name: 'group',
+        mpackVersion: Em.Object.create({
+          name: 'mpack',
+          selected: true,
+          services: [
+            Em.Object.create({
+              name: serviceName
+            })
+          ]
+        }),
+        serviceInstances: []
+      });
 
-      var service0 = Em.Object.create({
-        id: 0,
-        selected: true
+      var serviceInstanceName = 'instance';
+      var serviceInstance = Em.Object.create({
+        id: serviceGroup.get('name') + serviceInstanceName,
+        name: serviceInstanceName,
+        service: Em.Object.create({
+          name: serviceName
+        }),
+        serviceGroup: serviceGroup,
+        canRemove: true
       });
+      sinon.stub(wizardSelectMpacksController, 
'createServiceInstance').returns(serviceInstance);
 
-      var service1 = Em.Object.create({
-        id: 1,
-        selected: true
+      var addedServiceInstances = 
wizardSelectMpacksController.get('addedServiceInstances');
+      var countBefore = addedServiceInstances.length;
+
+      wizardSelectMpacksController.addServiceInstance(serviceName, 
serviceInstanceName, serviceGroup);
+
+      wizardSelectMpacksController.createServiceInstance.restore();
+
+      expect(addedServiceInstances.length).to.equal(countBefore + 1);
+      expect(addedServiceInstances[countBefore]).to.equal(serviceInstance) 
//last object pushed should be serviceInstance
+    });
+
+    it('should not add a duplicate service instance to addedServiceInstances', 
function () {
+      var serviceName = 'service';
+      var serviceInstanceName = 'instance';
+      
+      var serviceGroup = Em.Object.create({
+        name: 'group',
+        mpackVersion: Em.Object.create({
+          name: 'mpack',
+          selected: true,
+          services: [
+            Em.Object.create({
+              name: serviceName
+            })
+          ]
+        }),
+        //note this is not really how a serviceGroup object works in the real 
code
+        //serviceInstances is a FUNCTION that filters addedServiceInstances 
rather than an array
+        //but we need to mock this up this way for test purposes
+        serviceInstances: [
+          Em.Object.create({ name: serviceInstanceName })
+        ]
       });
 
-      actual = Em.Object.create({
-        selected: true,
-        services: [
-          service0,
-          service1
+      var serviceInstance = Em.Object.create({
+        id: serviceGroup.get('name') + serviceInstanceName,
+        name: serviceInstanceName,
+        service: Em.Object.create({
+          name: serviceName
+        }),
+        serviceGroup: serviceGroup,
+        canRemove: true
+      });
+      sinon.stub(wizardSelectMpacksController, 
'createServiceInstance').returns(serviceInstance);
+
+      //prime with the duplicate
+      wizardSelectMpacksController.addServiceInstance(serviceName, 
serviceInstanceName, serviceGroup);
+      var addedServiceInstances = 
wizardSelectMpacksController.get('addedServiceInstances');
+      var countBefore = addedServiceInstances.length;
+
+      //now try adding the same one again
+      wizardSelectMpacksController.addServiceInstance(serviceName, 
serviceInstanceName, serviceGroup);
+
+      wizardSelectMpacksController.createServiceInstance.restore();
+
+      expect(addedServiceInstances.length).to.equal(countBefore); //count 
should not change
+    });
+  });
+
+  describe('#removeServiceInstance', function () {
+    it('should remove a service instance from addedServiceInstances', function 
() {
+      var serviceInstance = Em.Object.create({
+        id: 'si1',
+        name: 'si1',
+        canRemove: true
+      });
+      
+      var serviceGroup = Em.Object.create({
+        //note this is not really how a serviceGroup object works in the real 
code
+        //serviceInstances is a FUNCTION that filters addedServiceInstances 
rather than an array
+        //but we need to mock this up this way for test purposes
+        serviceInstances: [
+          serviceInstance
         ]
       });
+      
+      wizardSelectMpacksController.set('addedServiceInstances', [
+        serviceInstance,  
+        Em.Object.create({
+          id: 'si2',
+          name: 'si2'
+        })
+      ]);
 
-      actual.get('services')[0].set('mpackVersion', actual);
-      actual.get('services')[1].set('mpackVersion', actual);
+      wizardSelectMpacksController.removeServiceInstance('si1', serviceGroup);
+      
expect(wizardSelectMpacksController.get('addedServiceInstances').length).to.equal(1);
+    });
+  });
 
-      sinon.stub(wizardSelectMpacksController, 
'getMpackVersionById').returns(actual);
-      sinon.stub(wizardSelectMpacksController, 'getServiceVersionById')
-        .withArgs(0).returns(service0)
-        .withArgs(1).returns(service1);
+  describe('#addMpackHandler', function () {
+    it('should make all calls required to add an mpack', function () {
+      var stub1 = sinon.stub(wizardSelectMpacksController, 
'addMpackVersion').returns(Em.Object.create({
+        services: [
+          Em.Object.create({}),
+          Em.Object.create({}),
+          Em.Object.create({})
+        ]
+      }));
+      var stub2 = sinon.stub(wizardSelectMpacksController, 
'addServiceGroup').returns(true);
+      var stub3 = sinon.stub(wizardSelectMpacksController, 
'addServiceInstance');
+      var stub4 = sinon.stub(wizardController, 'setStepUnsaved');
+
+      wizardSelectMpacksController.addMpackHandler();
+
+      expect(stub1.called).to.be.true;
+      expect(stub2.called).to.be.true;
+      expect(stub3.calledThrice).to.be.true;
+      expect(stub4.called).to.be.true;
+
+      wizardSelectMpacksController.addMpackVersion.restore();
+      wizardSelectMpacksController.addServiceGroup.restore();
+      wizardSelectMpacksController.addServiceInstance.restore();
+      wizardController.setStepUnsaved.restore();
     });
+  });
+
+  describe('#removeMpackHandler', function () {
+    it('should make all calls required to remove an mpack', function () {
+      var stub1 = sinon.stub(wizardSelectMpacksController, 
'getMpackVersionById').returns(Em.Object.create({}));
+      var stub2 = sinon.stub(wizardSelectMpacksController, 
'removeServiceGroup').returns(true);
+      var stub3 = sinon.stub(wizardSelectMpacksController, 
'removeMpackVersion');
+      var stub4 = sinon.stub(wizardController, 'setStepUnsaved');
+
+      wizardSelectMpacksController.removeMpackHandler();
+
+      expect(stub1.called).to.be.true;
+      expect(stub2.called).to.be.true;
+      expect(stub3.called).to.be.true;
+      expect(stub4.called).to.be.true;
 
-    after(function () {
       wizardSelectMpacksController.getMpackVersionById.restore();
+      wizardSelectMpacksController.removeServiceGroup.restore();
+      wizardSelectMpacksController.removeMpackVersion.restore();
+      wizardController.setStepUnsaved.restore();
+    });
+  });
+
+  describe('#addServiceHandler', function () {
+    var stub1, stub2, stub4, stub5;
+    
+    beforeEach(function () {
+      stub1 = sinon.stub(wizardSelectMpacksController, 
'addServiceVersion').returns(Em.Object.create({
+        mpackVersion: Em.Object.create({})
+      }));
+      stub2 = sinon.stub(wizardSelectMpacksController, 
'addMpackVersion').returns(Em.Object.create({}));
+      stub4 = sinon.stub(wizardSelectMpacksController, 'addServiceInstance');
+      stub5 = sinon.stub(wizardController, 'setStepUnsaved');
+    });
+
+    afterEach(function () {
+      wizardSelectMpacksController.addServiceVersion.restore();
+      wizardSelectMpacksController.addMpackVersion.restore();
+      wizardSelectMpacksController.addServiceInstance.restore();
+      wizardSelectMpacksController.getServiceGroup.restore();
+      wizardSelectMpacksController.addServiceGroup.restore();
+      wizardController.setStepUnsaved.restore();
+    });
+
+    it('should make all calls required to add a service to an existing service 
group', function () {
+      var stub3 = sinon.stub(wizardSelectMpacksController, 
'getServiceGroup').returns(true);
+      var stub3a = sinon.stub(wizardSelectMpacksController, 
'addServiceGroup').returns(true);
+
+      wizardSelectMpacksController.addServiceHandler();
+
+      expect(stub1.called).to.be.true;
+      expect(stub2.called).to.be.true;
+      expect(stub3.called).to.be.true;
+      expect(stub3a.called).to.be.false;
+      expect(stub4.called).to.be.true;
+      expect(stub5.called).to.be.true;
+    });
+
+    it('should make all calls required to add a service to a new service 
group', function () {
+      var stub3 = sinon.stub(wizardSelectMpacksController, 
'getServiceGroup').returns(false);
+      var stub3a = sinon.stub(wizardSelectMpacksController, 
'addServiceGroup').returns(true);
+
+      wizardSelectMpacksController.addServiceHandler();
+
+      expect(stub1.called).to.be.true;
+      expect(stub2.called).to.be.true;
+      expect(stub3.called).to.be.true;
+      expect(stub3a.called).to.be.true;
+      expect(stub4.called).to.be.true;
+      expect(stub5.called).to.be.true;
+    });
+  });
+
+  describe('#removeServiceHandler', function () {
+    var stub1, stub3, stub4, stub5, stub6;
+
+    beforeEach(function () {
+      stub1 = sinon.stub(wizardSelectMpacksController, 
'getServiceVersionById').returns(Em.Object.create({}));
+      stub3 = sinon.stub(wizardSelectMpacksController, 
'removeServiceInstance').returns(true);
+      stub4 = sinon.stub(wizardSelectMpacksController, 'removeServiceVersion');
+      stub5 = sinon.stub(wizardSelectMpacksController, 'removeMpackHandler');
+      stub6 = sinon.stub(wizardController, 'setStepUnsaved');
+    });
+
+    afterEach(function () {
       wizardSelectMpacksController.getServiceVersionById.restore();
+      wizardSelectMpacksController.removeServiceInstance.restore();
+      wizardSelectMpacksController.removeServiceVersion.restore();
+      wizardSelectMpacksController.removeMpackHandler.restore();
+      wizardController.setStepUnsaved.restore();
     });
 
-    it('should set the mpack and all its services to not selected and set the 
step to unsaved', function () {
-      var expected = Em.Object.create({
-        selected: false,
-        services: [
-          Em.Object.create({
-            id: 0,
-            selected: false
-          }),
-          Em.Object.create({
-            id: 1,
-            selected: false
-          })
-        ]
-      });
-      expected.get('services')[0].set('mpackVersion', expected);
-      expected.get('services')[1].set('mpackVersion', expected);
+    it('should make all calls required to remove a service instance', function 
() {
+      var stub2 = sinon.stub(wizardSelectMpacksController, 
'getServiceGroup').returns(Em.Object.create({
+        //note this is not really how a serviceGroup object works in the real 
code
+        //serviceInstances is a FUNCTION that filters addedServiceInstances 
rather than an array
+        //but we need to mock this up this way for test purposes
+        serviceInstances: [{}]
+      }));
 
-      wizardSelectMpacksController.removeMpackHandler("HDPCore3.0.0");
-      expect(actual).to.deep.equal(expected);
+      wizardSelectMpacksController.removeServiceHandler();
+
+      expect(stub1.called).to.be.true;
+      expect(stub2.called).to.be.true;
+      expect(stub3.called).to.be.true;
+      expect(stub4.called).to.be.true;
+      expect(stub5.called).to.be.false;
+      expect(stub6.called).to.be.true;
+
+      wizardSelectMpacksController.getServiceGroup.restore();
+    });
+
+    it('when removing the last service instance, it should also make the call 
to remove the service group', function () {
+      var stub2 = sinon.stub(wizardSelectMpacksController, 
'getServiceGroup').returns(Em.Object.create({
+        //note this is not really how a serviceGroup object works in the real 
code
+        //serviceInstances is a FUNCTION that filters addedServiceInstances 
rather than an array
+        //but we need to mock this up this way for test purposes
+        serviceInstances: []
+      }));
+
+      wizardSelectMpacksController.removeServiceHandler();
+
+      expect(stub1.called).to.be.true;
+      expect(stub2.called).to.be.true;
+      expect(stub3.called).to.be.true;
+      expect(stub4.called).to.be.true;
+      expect(stub5.called).to.be.true;
+      expect(stub6.called).to.be.true;
+
+      wizardSelectMpacksController.getServiceGroup.restore();
+    });
+  });
+
+  describe('#removeServiceGroupHandler', function () {
+    it('should make all calls required to remove a service group', function () 
{
+      var stub1 = sinon.stub(wizardSelectMpacksController, 
'getServiceGroup').returns(Em.Object.create({}));
+      var stub2 = sinon.stub(wizardSelectMpacksController, 
'removeMpackHandler');
+
+      wizardSelectMpacksController.removeServiceGroupHandler();
+
+      expect(stub1.called).to.be.true;
+      expect(stub2.called).to.be.true;
+    });
+  });
+
+  describe('#removeServiceInstanceHandler', function () {
+    it('should make all calls required to remove a service instance', function 
() {
+      sinon.stub(wizardSelectMpacksController, 'get').returns([
+        Em.Object.create({
+          serviceInstances: [
+            Em.Object.create({
+              id: '0'
+            })
+          ]
+        })
+      ]);
+      var stub1 = sinon.stub(wizardSelectMpacksController, 
'removeServiceHandler');
       
-      var final = Em.Object.create({
-        "0": true,
-        "1": false,
-        "2": true,
-        "3": true,
-        "4": true
-      });
-      var savedState = 
wizardSelectMpacksController.get('wizardController.content.stepsSavedState');
-      expect(savedState).to.deep.equal(final);
+      wizardSelectMpacksController.removeServiceInstanceHandler('0');
+
+      expect(stub1.called).to.be.true;
+
+      wizardSelectMpacksController.get.restore();
+      wizardSelectMpacksController.removeServiceHandler.restore();
     });
   });
 
   describe('#clearSelection', function () {
-    var initial, servicesActual, versionsActual, usecasesActual, expected, 
final;
+    var initial, servicesActual, versionsActual, useCasesActual, expected, 
final;
 
     beforeEach(function () {
       initial = Em.Object.create({
@@ -956,12 +1165,12 @@ describe('App.WizardSelectMpacksController', function () 
{
       ];
       wizardSelectMpacksController.set('content.mpackVersions', 
versionsActual);
 
-      usecasesActual = [
+      useCasesActual = [
         Em.Object.create({ selected: true }),
         Em.Object.create({ selected: true }),
         Em.Object.create({ selected: true })
       ];
-      wizardSelectMpacksController.set('content.mpackUsecases', 
usecasesActual);
+      wizardSelectMpacksController.set('content.mpackUseCases', 
useCasesActual);
 
       expected = [
         Em.Object.create({ selected: false }),
@@ -977,7 +1186,7 @@ describe('App.WizardSelectMpacksController', function () {
       expect(versionsActual).to.deep.equal(expected);
        
       //should not be changed
-      expect(usecasesActual).to.deep.equal([
+      expect(useCasesActual).to.deep.equal([
         Em.Object.create({ selected: true }),
         Em.Object.create({ selected: true }),
         Em.Object.create({ selected: true })
@@ -987,12 +1196,12 @@ describe('App.WizardSelectMpacksController', function () 
{
       expect(savedState).to.deep.equal(final);
     });
 
-    it('should set all usecases to be unselected in advanced mode', function 
() {
+    it('should set all useCases to be unselected in advanced mode', function 
() {
       wizardSelectMpacksController.set('content.advancedMode', true);
       wizardSelectMpacksController.clearSelection();
       expect(servicesActual).to.deep.equal(expected);
       expect(versionsActual).to.deep.equal(expected);
-      expect(usecasesActual).to.deep.equal(expected);
+      expect(useCasesActual).to.deep.equal(expected);
 
       var savedState = 
wizardSelectMpacksController.get('wizardController.content.stepsSavedState');
       expect(savedState).to.deep.equal(final);
@@ -1000,30 +1209,40 @@ describe('App.WizardSelectMpacksController', function 
() {
   });
 
   describe('#submit', function () {
-    it('should populate content.selectedServices, 
content.selectedServiceNames, and content.selectedMpacks', function () {
-      wizardSelectMpacksController.set('selectedServices', [
-        {
-          id: "id1",
-          name: "name1",
-          mpackVersion: {
-            mpack: { name: "mpackName1" },
-            name: "mpackName1",
-            version: "1.0.0.0"
-          }
-        },
-        {
-          id: "id2",
-          name: "name2",
-          mpackVersion: {
-            mpack: { name: "mpackName2" },
-            name: "mpackName2",
-            version: "1.0.0.0"
-          }
-        }
+    it('should populate content.addedServiceGroups, 
content.addedServiceInstances, and content.selectedMpacks', function () {
+      wizardSelectMpacksController.set('addedServiceGroups', [
+        Em.Object.create({
+          name: 'name',
+          mpackVersion: Em.Object.create({
+            mpack: Em.Object.create({
+              name: 'mpackName'
+            }),
+            version: 'version'
+          })
+        })
       ]);
 
+      wizardSelectMpacksController.set('addedServiceInstances', [
+        Em.Object.create({
+          name: 'instanceName',
+          service: Em.Object.create({
+            id: '0',
+            name: 'name',
+            mpackVersion: Em.Object.create({
+              mpack: Em.Object.create({
+                name: 'mpackName'
+              }),
+              version: 'version'
+            })
+          }),
+          serviceGroup: Em.Object.create({
+            name: 'groupName'
+          })
+        })
+      ]);
+      
       wizardSelectMpacksController.set('selectedMpackVersions', [
-        {
+        Em.Object.create({
           mpack: {
             name: "mpackName1",
             displayName: "displayName1",
@@ -1031,8 +1250,8 @@ describe('App.WizardSelectMpacksController', function () {
           },
           mpackUrl: "http://someurl.com/mpack1";,
           version: "1.0.0.0"
-        },
-        {
+        }),
+        Em.Object.create({
           mpack: {
             name: "mpackName2",
             displayName: "displayName2",
@@ -1040,27 +1259,26 @@ describe('App.WizardSelectMpacksController', function 
() {
           },
           mpackUrl: "http://someurl.com/mpack2";,
           version: "1.0.0.0"
-        }
+        })
       ]);
       
-      var expectedSelectedServices = [
-        {
-          id: "id1",
-          name: "name1",
-          mpackName: "mpackName1",
-          mpackVersion: "1.0.0.0"
-        },
+      var expectedAddedServiceGroups = [
         {
-          id: "id2",
-          name: "name2",
-          mpackName: "mpackName2",
-          mpackVersion: "1.0.0.0"
+          name: 'name',
+          mpackName: 'mpackName',
+          mpackVersion: 'version'
         }
       ];
 
-      var expectedSelectedServiceNames = [
-        "name1",
-        "name2"
+      var expectedAddedServiceInstances = [
+        {
+          id: '0',
+          name: 'instanceName',
+          serviceGroupName: 'groupName',
+          serviceName: 'name',
+          mpackName: 'mpackName',
+          mpackVersion: 'version'
+        }
       ];
 
       var expectedSelectedMpacks = [
@@ -1088,13 +1306,13 @@ describe('App.WizardSelectMpacksController', function 
() {
       wizardSelectMpacksController.submit();
       App.router.send.restore();
       
-      
expect(wizardSelectMpacksController.get('content.selectedServices')).to.deep.equal(expectedSelectedServices);
-      
expect(wizardSelectMpacksController.get('content.selectedServiceNames')).to.deep.equal(expectedSelectedServiceNames);
+      
expect(wizardSelectMpacksController.get('content.addedServiceGroups')).to.deep.equal(expectedAddedServiceGroups);
+      
expect(wizardSelectMpacksController.get('content.addedServiceInstances')).to.deep.equal(expectedAddedServiceInstances);
       
expect(wizardSelectMpacksController.get('content.selectedMpacks')).to.deep.equal(expectedSelectedMpacks);
     });
   });
 
-  describe('#toggleUsecaseHandler', function () {
+  describe('#toggleUseCaseHandler', function () {
     beforeEach(function () {
       var initial = Em.Object.create({
         "0": true,
@@ -1105,7 +1323,7 @@ describe('App.WizardSelectMpacksController', function () {
       })
       
wizardSelectMpacksController.set('wizardController.content.stepsSavedState', 
initial);
 
-      sinon.stub(wizardSelectMpacksController, 
'getUsecaseRecommendation').returns({
+      sinon.stub(wizardSelectMpacksController, 
'getUseCaseRecommendation').returns({
         done: sinon.stub().returns({ fail: sinon.stub() })
       });
     });
@@ -1121,27 +1339,27 @@ describe('App.WizardSelectMpacksController', function 
() {
       var savedState = 
wizardSelectMpacksController.get('wizardController.content.stepsSavedState');
       expect(savedState).to.deep.equal(final);
 
-      wizardSelectMpacksController.getUsecaseById.restore();
-      wizardSelectMpacksController.getUsecaseRecommendation.restore();
+      wizardSelectMpacksController.getUseCaseById.restore();
+      wizardSelectMpacksController.getUseCaseRecommendation.restore();
     });
 
-    it('should select usecase when it is not already selected', function () {
+    it('should select useCase when it is not already selected', function () {
       var actual = Em.Object.create({
         selected: false
       });
 
-      wizardSelectMpacksController.set('content.mpackUsecases', [
+      wizardSelectMpacksController.set('content.mpackUseCases', [
         Em.Object.create({
           selected: false
         }),
         actual
       ]);
 
-      sinon.stub(wizardSelectMpacksController, 
'getUsecaseById').returns(actual);
+      sinon.stub(wizardSelectMpacksController, 
'getUseCaseById').returns(actual);
 
-      wizardSelectMpacksController.toggleUsecaseHandler("DataScience");
+      wizardSelectMpacksController.toggleUseCaseHandler("DataScience");
       expect(actual.get('selected')).to.be.true;
-      
expect(wizardSelectMpacksController.getUsecaseRecommendation).to.have.been.called;
+      
expect(wizardSelectMpacksController.getUseCaseRecommendation).to.have.been.called;
     });
 
     it('should deselect use case when it is already selected', function () {
@@ -1149,41 +1367,41 @@ describe('App.WizardSelectMpacksController', function 
() {
         selected: true
       });
       
-      wizardSelectMpacksController.set('content.mpackUsecases', [
+      wizardSelectMpacksController.set('content.mpackUseCases', [
         Em.Object.create({
           selected: true
         }),
         actual
       ]);
       
-      sinon.stub(wizardSelectMpacksController, 
'getUsecaseById').returns(actual);
+      sinon.stub(wizardSelectMpacksController, 
'getUseCaseById').returns(actual);
       sinon.stub(wizardSelectMpacksController, 'clearSelection');
 
-      wizardSelectMpacksController.toggleUsecaseHandler("DataScience");
+      wizardSelectMpacksController.toggleUseCaseHandler("DataScience");
       expect(actual.get('selected')).to.be.false;
-      
expect(wizardSelectMpacksController.getUsecaseRecommendation).to.have.been.called;
+      
expect(wizardSelectMpacksController.getUseCaseRecommendation).to.have.been.called;
       
       wizardSelectMpacksController.clearSelection.restore();
     });
 
-    it('should not call getUsecaseRecommendation when no use cases are 
selected', function () {
+    it('should not call getUseCaseRecommendation when no use cases are 
selected', function () {
       var actual = Em.Object.create({
         selected: true
       });
 
-      wizardSelectMpacksController.set('content.mpackUsecases', [
+      wizardSelectMpacksController.set('content.mpackUseCases', [
         Em.Object.create({
           selected: false
         }),
         actual
       ]);
 
-      sinon.stub(wizardSelectMpacksController, 
'getUsecaseById').returns(actual);
+      sinon.stub(wizardSelectMpacksController, 
'getUseCaseById').returns(actual);
       sinon.stub(wizardSelectMpacksController, 'clearSelection');
 
-      wizardSelectMpacksController.toggleUsecaseHandler("DataScience");
+      wizardSelectMpacksController.toggleUseCaseHandler("DataScience");
       expect(actual.get('selected')).to.be.false;
-      
expect(wizardSelectMpacksController.getUsecaseRecommendation).to.not.have.been.called;
+      
expect(wizardSelectMpacksController.getUseCaseRecommendation).to.not.have.been.called;
 
       wizardSelectMpacksController.clearSelection.restore();
     });
diff --git 
a/ambari-web/test/controllers/wizard/step7/assign_master_controller_test.js 
b/ambari-web/test/controllers/wizard/step7/assign_master_controller_test.js
index 16c9681..3a66b9e 100644
--- a/ambari-web/test/controllers/wizard/step7/assign_master_controller_test.js
+++ b/ambari-web/test/controllers/wizard/step7/assign_master_controller_test.js
@@ -295,7 +295,7 @@ describe('App.AssignMasterOnStep7Controller', function () {
     });
   });
 
-  describe("#getAllMissingDependentServices()", function () {
+  describe("#getAllMissingDependentServices", function () {
 
     beforeEach(function() {
       sinon.stub(App.StackServiceComponent, 'find').returns(Em.Object.create({
@@ -322,7 +322,7 @@ describe('App.AssignMasterOnStep7Controller', function () {
       App.StackService.find.restore();
     });
 
-    it("test", function() {
+    it("should return missing dependencies", function() {
       view.set('configActionComponent', Em.Object.create({
         componentName: 'C1'
       }));
diff --git a/ambari-web/test/controllers/wizard_test.js 
b/ambari-web/test/controllers/wizard_test.js
index 33c6bd2..e0d577f 100644
--- a/ambari-web/test/controllers/wizard_test.js
+++ b/ambari-web/test/controllers/wizard_test.js
@@ -19,7 +19,7 @@
 var App = require('app');
 require('models/cluster');
 require('controllers/wizard');
-
+var testHelpers = require('test/helpers');
 var c;
 
 function getSteps(start, count) {
@@ -202,7 +202,7 @@ describe('App.WizardController', function () {
     afterEach(function () {
       wizardController.getDBProperties.restore();
     });
-    it('should load service confgig group', function () {
+    it('should load service config group', function () {
       wizardController.loadServiceConfigGroups();
       expect(wizardController.get('content.configGroups')).to.eql([
         {
@@ -1984,4 +1984,96 @@ describe('App.WizardController', function () {
       expect(stepName).to.equal("step2");
     });
   });
+
+  describe("#loadRegisteredMpacks", function() {
+    it("should send ajax request if local db is not populated", function() {
+      sinon.stub(wizardController, 'getDBProperty').returns(false);
+
+      wizardController.loadRegisteredMpacks();
+      var args = testHelpers.findAjaxRequest('name', 
'mpack.get_registered_mpacks');
+      expect(args).to.be.exist;
+      
+      wizardController.getDBProperty.restore();
+    });
+
+    it("should populate content.registeredMpacks", function() {
+      var expected = [];
+
+      sinon.stub(wizardController, 'getDBProperty').returns(expected);
+
+      wizardController.loadRegisteredMpacks();
+      
expect(wizardController.get('content.registeredMpacks')).to.equal(expected);
+      
+      wizardController.getDBProperty.restore();
+    });
+  });
+
+  describe("#loadRegisteredMpacksCallback", function() {
+    it("should call setDBProperty and populate content.registeredMpacks", 
function () {
+      var expected = [];
+      var stub = sinon.stub(wizardController, 'setDBProperty');
+      
+      wizardController.loadRegisteredMpacksCallback({ items: expected });
+      expect(stub.called).to.be.true;
+      
expect(wizardController.get('content.registeredMpacks')).to.equal(expected);
+
+      wizardController.setDBProperty.restore();
+    });
+  });
+
+  describe("#loadServiceGroups", function() {
+    it("should send ajax request if local db is not populated", function() {
+      sinon.stub(wizardController, 'getDBProperty').returns(false);
+
+      wizardController.loadServiceGroups();
+      var args = testHelpers.findAjaxRequest('name', 
'servicegroup.get_all_details');
+      expect(args).to.be.exist;
+      
+      wizardController.getDBProperty.restore();
+    });
+
+    it("should populate content.serviceGroups and content.serviceInstances", 
function() {
+      var serviceGroups = 'serviceGroups';
+      var serviceInstances = 'serviceInstances';
+      var stub = sinon.stub(wizardController, 'getDBProperty');
+      stub.withArgs(serviceGroups).returns(serviceGroups);
+      stub.withArgs(serviceInstances).returns(serviceInstances);
+
+      wizardController.loadServiceGroups();
+      
expect(wizardController.get('content.serviceGroups')).to.equal(serviceGroups);
+      
expect(wizardController.get('content.serviceInstances')).to.equal(serviceInstances);
+      
+      wizardController.getDBProperty.restore();
+    });
+  });
+
+  describe("#loadServiceGroupsCallback", function() {
+    it("should call setDBProperty and populate content.serviceGroups and 
content.serviceInstances", function () {
+      var data = [{
+        service_group_name: 'name',
+        mpack_name: 'mpackName',
+        mpack_version: 'version',
+        services: [{
+          ServiceInfo: {
+            service_name: 'serviceName',
+            service_group_name: 'name'
+          }
+        }]
+      }];
+      var stub = sinon.stub(wizardController, 'setDBProperty');
+      
+      wizardController.loadServiceGroupsCallback({ items: data });
+      expect(stub.calledTwice).to.be.true;
+      expect(wizardController.get('content.serviceGroups')).to.deep.equal([{
+        name: 'name',
+        mpackVersionId: 'mpackNameversion'
+      }]);
+      expect(wizardController.get('content.serviceInstances')).to.deep.equal([{
+        name: 'serviceName',
+        serviceGroupName: 'name'
+      }]);
+
+      wizardController.setDBProperty.restore();
+    });
+  });
 });
diff --git a/ambari-web/test/mixins/wizard/assign_master_components_test.js 
b/ambari-web/test/mixins/wizard/assign_master_components_test.js
index 20efb8e..e740e12 100644
--- a/ambari-web/test/mixins/wizard/assign_master_components_test.js
+++ b/ambari-web/test/mixins/wizard/assign_master_components_test.js
@@ -211,6 +211,6 @@ describe('App.AssignMasterComponents', function () {
   });
 
   App.TestAliases.testAsComputedOr(baseObject.create(),
-    'nextButtonDisabled', ['App.router.btnClickInProgress', 'submitDisabled', 
'validationInProgress', '!isLoaded']);
+    'nextButtonDisabled', ['App.router.btnClickInProgress', 'submitDisabled', 
'validationInProgress', '!isLoaded', '!hasServiceGroups']);
 
 });
\ No newline at end of file

Reply via email to