Преглед изворни кода

AMBARI-2167. Support for displaying various stacks and having the user select which stack to install. (yusaku)

git-svn-id: https://svn.apache.org/repos/asf/incubator/ambari/trunk@1485444 13f79535-47bb-0310-9956-ffa450edef68
Yusaku Sako пре 12 година
родитељ
комит
f126a5c12c

+ 3 - 0
CHANGES.txt

@@ -12,6 +12,9 @@ Trunk (unreleased changes):
 
  NEW FEATURES
 
+ AMBARI-2167. Support for displaying various stacks and having the user select
+ which stack to install. (yusaku)
+
  AMBARI-2163. Add smoke tests as part of starting all services in
  security wizard. (jaimin)
 

+ 82 - 0
ambari-web/app/assets/data/wizard/stack/versions.json

@@ -0,0 +1,82 @@
+{
+  "href" : "http://dev.hortonworks.com:8080/api/v1/stacks2?fields=versions",
+  "items" : [
+    {
+      "href" : "http://dev.hortonworks.com:8080/api/v1/stacks2/HDP",
+      "Stacks" : {
+        "stack_name" : "HDP"
+      },
+      "versions" : [
+        {
+          "href" : "http://dev.hortonworks.com:8080/api/v1/stacks2/HDP/versions/0.1",
+          "Versions" : {
+            "stack_version" : "0.1",
+            "stack_name" : "HDP",
+            "active" : "false"
+          }
+        },
+        {
+          "href" : "http://dev.hortonworks.com:8080/api/v1/stacks2/HDP/versions/2.0.1",
+          "Versions" : {
+            "stack_version" : "2.0.1",
+            "stack_name" : "HDP",
+            "active" : "false"
+          }
+        },
+        {
+          "href" : "http://dev.hortonworks.com:8080/api/v1/stacks2/HDP/versions/1.3.0",
+          "Versions" : {
+            "stack_version" : "1.3.0",
+            "stack_name" : "HDP",
+            "active" : "true"
+          }
+        },
+        {
+          "href" : "http://dev.hortonworks.com:8080/api/v1/stacks2/HDP/versions/1.2.1",
+          "Versions" : {
+            "stack_version" : "1.2.1",
+            "stack_name" : "HDP",
+            "active" : "true"
+          }
+        },
+        {
+          "href" : "http://dev.hortonworks.com:8080/api/v1/stacks2/HDP/versions/1.2.0",
+          "Versions" : {
+            "stack_version" : "1.2.0",
+            "stack_name" : "HDP",
+            "active" : "false"
+          }
+        }
+      ]
+    },
+    {
+      "href" : "http://dev.hortonworks.com:8080/api/v1/stacks2/HDPLocal",
+      "Stacks" : {
+        "stack_name" : "HDPLocal"
+      },
+      "versions" : [
+        {
+          "href" : "http://dev.hortonworks.com:8080/api/v1/stacks2/HDPLocal/versions/1.2.1",
+          "Versions" : {
+            "stack_version" : "1.2.1",
+            "stack_name" : "HDPLocal"
+          }
+        },
+        {
+          "href" : "http://dev.hortonworks.com:8080/api/v1/stacks2/HDPLocal/versions/1.3.0",
+          "Versions" : {
+            "stack_version" : "1.3.0",
+            "stack_name" : "HDPLocal"
+          }
+        },
+        {
+          "href" : "http://dev.hortonworks.com:8080/api/v1/stacks2/HDPLocal/versions/1.2.0",
+          "Versions" : {
+            "stack_version" : "1.2.0",
+            "stack_name" : "HDPLocal"
+          }
+        }
+      ]
+    }
+  ]
+}

+ 1 - 0
ambari-web/app/controllers.js

@@ -85,6 +85,7 @@ require('controllers/main/mirroring/targetClusterController');
 require('controllers/main/mirroring/testConnection_controller');
 require('controllers/main/mirroring/testConnectionResults_controller');
 require('controllers/wizard/slave_component_groups_controller');
+require('controllers/wizard/step0_controller');
 require('controllers/wizard/step1_controller');
 require('controllers/wizard/step2_controller');
 require('controllers/wizard/step3_controller');

+ 90 - 2
ambari-web/app/controllers/installer.js

@@ -23,7 +23,7 @@ App.InstallerController = App.WizardController.extend({
 
   name: 'installerController',
 
-  totalSteps: 10,
+  totalSteps: 11,
 
   content: Em.Object.create({
     cluster: null,
@@ -38,6 +38,15 @@ App.InstallerController = App.WizardController.extend({
     controllerName: 'installerController'
   }),
 
+  init: function () {
+    this._super();
+    this.get('isStepDisabled').setEach('value', true);
+    this.get('isStepDisabled').pushObject(Ember.Object.create({
+      step: 0,
+      value: false
+    }));
+  },
+
   getCluster: function(){
     return jQuery.extend({}, this.get('clusterStatusTemplate'));
   },
@@ -82,6 +91,73 @@ App.InstallerController = App.WizardController.extend({
     console.log('selected services ', servicesInfo.filterProperty('isSelected', true).mapProperty('serviceName'));
   },
 
+  /**
+   * Load stacks data from server or take exist data from local db
+   */
+  loadStacks: function () {
+    var stacks = App.db.getStacks();
+    if (stacks) {
+      var convertedStacks = [];
+      stacks.forEach(function (stack) {
+        convertedStacks.pushObject(Ember.Object.create(stack));
+      });
+      this.set('content.stacks', convertedStacks);
+    } else {
+      App.ajax.send({
+        name: 'wizard.stacks',
+        sender: this,
+        success: 'loadStacksSuccessCallback',
+        error: 'loadStacksErrorCallback'
+      });
+    }
+  },
+
+  /**
+   * Parse loaded data and create array of stacks objects
+   */
+  loadStacksSuccessCallback: function (data) {
+    var result = [];
+    var stacks = data.items;
+    stacks.forEach(function (stack) {
+      if (stack.Stacks.stack_name.indexOf('Local') === -1) {
+        stack.versions.sort(function (a, b) {
+          if (a.Versions.stack_version > b.Versions.stack_version) {
+            return -1;
+          }
+          if (a.Versions.stack_version < b.Versions.stack_version) {
+            return 1;
+          }
+          return 0;
+        });
+        stack.versions.forEach(function (version) {
+          if (version.Versions.active !== 'false') {
+            result.push(
+                Ember.Object.create({
+                  name: version.Versions.stack_name + " " + version.Versions.stack_version,
+                  isSelected: false
+                })
+            );
+          }
+        }, this)
+      }
+    }, this);
+    var defaultStackVersion = result.findProperty('name', App.defaultStackVersion.replace('-', ' '));
+    if (defaultStackVersion) {
+      defaultStackVersion.set('isSelected', true)
+    } else {
+      result.objectAt(0).set('isSelected', true);
+    }
+    App.db.setStacks(result);
+    this.set('content.stacks', result);
+  },
+
+  /**
+   * onError callback for loading stacks data
+   */
+  loadStacksErrorCallback: function () {
+    console.log('Error in loading stacks');
+  },
+
   /**
    * Save data to model
    * @param stepController App.WizardStep4Controller
@@ -182,6 +258,16 @@ App.InstallerController = App.WizardController.extend({
     console.log("InstallerController.saveClients: saved list ", clients);
   },
 
+  /**
+   * Save stacks data to local db
+   * @param stepController step1WizardController
+   */
+  saveStacks: function (stepController) {
+    var stacks = stepController.get('content.stacks');
+    App.db.setStacks(stacks);
+    this.set('content.stacks', stacks);
+  },
+
   /**
    * Load data for all steps until <code>current step</code>
    */
@@ -206,6 +292,8 @@ App.InstallerController = App.WizardController.extend({
       case '2':
         this.load('installOptions');
       case '1':
+        this.loadStacks();
+      case '0':
         this.load('cluster');
     }
   },
@@ -213,7 +301,7 @@ App.InstallerController = App.WizardController.extend({
    * Clear all temporary data
    */
   finish: function () {
-    this.setCurrentStep('1');
+    this.setCurrentStep('0');
     this.clearStorageData();
   }
 

+ 8 - 0
ambari-web/app/controllers/wizard.js

@@ -82,6 +82,10 @@ App.WizardController = Em.Controller.extend({
 
   clusters: null,
 
+  isStep0: function () {
+    return this.get('currentStep') == 0;
+  }.property('currentStep'),
+
   isStep1: function () {
     return this.get('currentStep') == 1;
   }.property('currentStep'),
@@ -150,6 +154,10 @@ App.WizardController = Em.Controller.extend({
     }
   },
 
+  gotoStep0: function () {
+    this.gotoStep(0);
+  },
+
   gotoStep1: function () {
     this.gotoStep(1);
   },

+ 74 - 0
ambari-web/app/controllers/wizard/step0_controller.js

@@ -0,0 +1,74 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+var App = require('app');
+
+App.WizardStep0Controller = Em.Controller.extend({
+  name: 'wizardStep0Controller',
+
+  hasSubmitted : false,
+
+  loadStep: function () {
+    this.set('hasSubmitted',false);
+  },
+  /**
+   * validate cluster name
+   * set clusterNameError if validation fails
+   */
+  invalidClusterName : function(){
+    if(!this.get('hasSubmitted')){
+      this.set('clusterNameError', '');
+      return false;
+    }
+
+    var clusterName = this.get('content.cluster.name');
+    if (clusterName == '') {
+      this.set('clusterNameError', Em.I18n.t('installer.step0.clusterName.error.required'));
+      return true;
+    } else if (/\s/.test(clusterName)) {
+      this.set('clusterNameError', Em.I18n.t('installer.step0.clusterName.error.whitespaces'));
+      return true;
+    } else if (/[^\w\s]/gi.test(clusterName)) {
+      this.set('clusterNameError', Em.I18n.t('installer.step0.clusterName.error.specialChar'));
+      return true;
+    } else {
+      this.set('clusterNameError', '');
+      return false;
+    }
+  }.property('hasSubmitted', 'content.cluster.name').cacheable(),
+
+  /**
+   * calculates by <code>invalidClusterName</code> property
+   */
+  //todo: mix this and previous variables in one
+  clusterNameError: '',
+
+  /**
+   * Onclick handler for <code>next</code> button
+   */
+  submit: function () {
+    this.set('hasSubmitted', true);
+    if (!this.get('invalidClusterName')) {
+      App.clusterStatus.set('clusterName', this.get('content.cluster.name'));
+      this.set('content.cluster.status', 'PENDING');
+      this.set('content.cluster.isCompleted', false);
+      App.router.send('next');
+    }
+  }
+
+});

+ 1 - 52
ambari-web/app/controllers/wizard/step1_controller.js

@@ -19,56 +19,5 @@
 var App = require('app');
 
 App.WizardStep1Controller = Em.Controller.extend({
-  name: 'wizardStep1Controller',
-
-  hasSubmitted : false,
-
-  loadStep: function () {
-    this.set('hasSubmitted',false);
-  },
-  /**
-   * validate cluster name
-   * set clusterNameError if validation fails
-   */
-  invalidClusterName : function(){
-    if(!this.get('hasSubmitted')){
-      this.set('clusterNameError', '');
-      return false;
-    }
-
-    var clusterName = this.get('content.cluster.name');
-    if (clusterName == '') {
-      this.set('clusterNameError', Em.I18n.t('installer.step1.clusterName.error.required'));
-      return true;
-    } else if (/\s/.test(clusterName)) {
-      this.set('clusterNameError', Em.I18n.t('installer.step1.clusterName.error.whitespaces'));
-      return true;
-    } else if (/[^\w\s]/gi.test(clusterName)) {
-      this.set('clusterNameError', Em.I18n.t('installer.step1.clusterName.error.specialChar'));
-      return true;
-    } else {
-      this.set('clusterNameError', '');
-      return false;
-    }
-  }.property('hasSubmitted', 'content.cluster.name').cacheable(),
-
-  /**
-   * calculates by <code>invalidClusterName</code> property
-   */
-  //todo: mix this and previous variables in one
-  clusterNameError: '',
-
-  /**
-   * Onclick handler for <code>next</code> button
-   */
-  submit: function () {
-    this.set('hasSubmitted', true);
-    if (!this.get('invalidClusterName')) {
-      App.clusterStatus.set('clusterName', this.get('content.cluster.name'));
-      this.set('content.cluster.status', 'PENDING');
-      this.set('content.cluster.isCompleted', false);
-      App.router.send('next');
-    }
-  }
-
+  name: 'wizardStep1Controller'
 });

+ 13 - 9
ambari-web/app/messages.js

@@ -131,6 +131,7 @@ Em.I18n.translations = {
   'common.install': 'Install',
   'common.errorPopup.header': 'An error has been encountered',
   'common.use': 'Use',
+  'common.stacks': 'Stacks',
 
   'requestInfo.installComponents':'Install Components',
   'requestInfo.installServices':'Install Services',
@@ -221,16 +222,19 @@ Em.I18n.translations = {
   'installer.controls.addSlaveComponentGroupButton.content':'If you need different settings on certain {0}s, you can add a {1} group.<br>All {2}s within the same group will have the same set of settings.  You can create multiple groups.',
   'installer.controls.slaveComponentChangeGroupName.error':'group with this name already exist',
 
-  'installer.step1.header':'Welcome',
-  'installer.step1.body.header':'Welcome to Apache Ambari!',
-  'installer.step1.body':'Ambari makes it easy to install, manage, and monitor Hadoop clusters.<br>' +
+  'installer.step0.header':'Welcome',
+  'installer.step0.body.header':'Welcome to Apache Ambari!',
+  'installer.step0.body':'Ambari makes it easy to install, manage, and monitor Hadoop clusters.<br>' +
     'We will walk you through the cluster installation process with this step-by-step wizard.',
-  'installer.step1.clusterName':'Name your cluster',
-  'installer.step1.clusterName.tooltip.title':'Cluster Name',
-  'installer.step1.clusterName.tooltip.content':'Enter a unique cluster name. Cluster name cannot be changed later.',
-  'installer.step1.clusterName.error.required':'Cluster Name is required',
-  'installer.step1.clusterName.error.whitespaces':'Cluster Name cannot contain white spaces',
-  'installer.step1.clusterName.error.specialChar':'Cluster Name cannot contain special characters',
+  'installer.step0.clusterName':'Name your cluster',
+  'installer.step0.clusterName.tooltip.title':'Cluster Name',
+  'installer.step0.clusterName.tooltip.content':'Enter a unique cluster name. Cluster name cannot be changed later.',
+  'installer.step0.clusterName.error.required':'Cluster Name is required',
+  'installer.step0.clusterName.error.whitespaces':'Cluster Name cannot contain white spaces',
+  'installer.step0.clusterName.error.specialChar':'Cluster Name cannot contain special characters',
+
+  'installer.step1.header':'Select Stack',
+  'installer.step1.body':'Please select the service stack that you want to use to install your Hadoop cluster.',
 
   'installer.step2.header':'Install Options',
   'installer.step2.body':'Enter the list of hosts to be included in the cluster and provide your SSH key.',

+ 1 - 1
ambari-web/app/router.js

@@ -73,7 +73,7 @@ App.Router = Em.Router.extend({
     var currentStep = App.db.getWizardCurrentStep(wizardType);
     console.log('getWizardCurrentStep: loginName=' + loginName + ", currentStep=" + currentStep);
     if (!currentStep) {
-      currentStep = '1';
+      currentStep = wizardType === 'installer' ? '0' : '1';
     }
     console.log('returning currentStep=' + currentStep);
     return currentStep;

+ 22 - 2
ambari-web/app/routes/installer.js

@@ -102,6 +102,23 @@ module.exports = Em.Route.extend({
     router.get('applicationController').connectOutlet('installer');
   },
 
+  step0: Em.Route.extend({
+    route: '/step0',
+    connectOutlets: function (router) {
+      console.log('in installer.step0:connectOutlets');
+      var controller = router.get('installerController');
+      controller.setCurrentStep('0');
+      controller.loadAllPriorSteps();
+      controller.connectOutlet('wizardStep0', controller.get('content'));
+    },
+
+    next: function (router) {
+      var installerController = router.get('installerController');
+      installerController.save('cluster');
+      router.transitionTo('step1');
+    }
+  }),
+
   step1: Em.Route.extend({
     route: '/step1',
     connectOutlets: function (router) {
@@ -111,10 +128,11 @@ module.exports = Em.Route.extend({
       controller.loadAllPriorSteps();
       controller.connectOutlet('wizardStep1', controller.get('content'));
     },
-
+    back: Em.Router.transitionTo('step0'),
     next: function (router) {
+      var wizardStep1Controller = router.get('wizardStep1Controller');
       var installerController = router.get('installerController');
-      installerController.save('cluster');
+      installerController.saveStacks(wizardStep1Controller);
       installerController.clearInstallOptions();
       router.transitionTo('step2');
     }
@@ -359,6 +377,8 @@ module.exports = Em.Route.extend({
     }
   }),
 
+  gotoStep0: Em.Router.transitionTo('step0'),
+
   gotoStep1: Em.Router.transitionTo('step1'),
 
   gotoStep2: Em.Router.transitionTo('step2'),

+ 1 - 0
ambari-web/app/templates/installer.hbs

@@ -25,6 +25,7 @@
           <div class="well">
             <ul class="nav nav-pills nav-stacked">
               <li class="nav-header">{{t installer.header}}</li>
+              <li {{bindAttr class="isStep0:active view.isStep0Disabled:disabled"}}><a href="javascript:void(null);"  {{action gotoStep0 target="controller"}}>{{t installer.step0.header}}</a></li>
               <li {{bindAttr class="isStep1:active view.isStep1Disabled:disabled"}}><a href="javascript:void(null);"  {{action gotoStep1 target="controller"}}>{{t installer.step1.header}}</a></li>
               <li {{bindAttr class="isStep2:active view.isStep2Disabled:disabled"}}><a href="javascript:void(null);"  {{action gotoStep2 target="controller"}}>{{t installer.step2.header}}</a></li>
               <li {{bindAttr class="isStep3:active view.isStep3Disabled:disabled"}}><a href="javascript:void(null);"  {{action gotoStep3 target="controller"}}>{{t installer.step3.header}}</a></li>

+ 9 - 22
ambari-web/app/templates/wizard/step1.hbs

@@ -15,28 +15,15 @@
 * See the License for the specific language governing permissions and
 * limitations under the License.
 }}
-
-<h2>{{t installer.step1.body.header}}</h2>
+<h2>{{t installer.step1.header}}</h2>
 <p class="alert alert-info">
   {{t installer.step1.body}}
 </p>
-<div {{bindAttr class="view.onError:error :control-group"}}>
-  <label class="control-label" for="cluster-name">{{t installer.step1.clusterName}}
-    <a href="javascript:void(null)"
-       rel="popover"
-      {{translateAttr title="installer.step1.clusterName.tooltip.title"
-       data-content="installer.step1.clusterName.tooltip.content"}}>{{t common.learnMore}}</a>
-  </label>
-
-  <div class="controls">
-    {{view App.WizardStep1ViewClusterNameInput valueBinding="content.cluster.name" placeholder="cluster name" target="controller"}}
-    <p class="help-inline">{{clusterNameError}}</p>
-  </div>
-</div>
-
-<div class="btn-area">
-  <a class="btn btn-success pull-right" {{bindAttr disabled="invalidClusterName"}} {{action "submit" target="controller"}}>{{t common.next}} &rarr;</a>
-</div>
-
-
-
+<p><b>{{t common.stacks}}</b></p>
+<form>
+  {{#each stack in view.stacks}}
+    <label class="radio">{{view view.stackRadioButton contentBinding="stack"}} {{stack.name}}</label>
+  {{/each}}
+</form>
+<a class="btn pull-left" {{action back}}>&larr; {{t common.back}}</a>
+<a class="btn btn-success pull-right" {{action next}}>{{t common.next}} &rarr;</a>

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

@@ -655,6 +655,15 @@ var urls = {
     'real': '/hosts',
     'mock': '/data/wizard/bootstrap/single_host_registration.json'
   },
+  'wizard.stacks': {
+    'real': '/stacks2?fields=versions',
+    'mock': '/data/wizard/stack/versions.json',
+    'format': function (data) {
+      return {
+        async: false
+      };
+    }
+  },
   'router.login': {
     'real': '/users/{loginName}',
     'mock': '/data/users/user_{usr}.json',

+ 11 - 0
ambari-web/app/utils/db.js

@@ -236,6 +236,12 @@ App.db.setReassignTasksStatuses = function (tasksStatuses) {
   localStorage.setObject('ambari', App.db.data);
 };
 
+App.db.setStacks = function (stacks) {
+  App.db.data = localStorage.getObject('ambari');
+  App.db.data.app.stacksVersions = stacks;
+  localStorage.setObject('ambari', App.db.data);
+};
+
 /**
  * Set current step value for specified Wizard Type
  * @param wizardType
@@ -425,4 +431,9 @@ App.db.getSecurityStage = function () {
 
 };
 
+App.db.getStacks = function () {
+  App.db.data = localStorage.getObject('ambari');
+  return App.db.data.app.stacksVersions;
+};
+
 module.exports = App.db;

+ 1 - 0
ambari-web/app/views.js

@@ -133,6 +133,7 @@ require('views/main/mirroring/testConnection_view');
 require('views/main/mirroring/testConnectionResults_view');
 require('views/installer');
 require('views/wizard/controls_view');
+require('views/wizard/step0_view');
 require('views/wizard/step1_view');
 require('views/wizard/step2_view');
 require('views/wizard/step3_view');

+ 4 - 0
ambari-web/app/views/installer.js

@@ -23,6 +23,10 @@ App.InstallerView = Em.View.extend({
 
   templateName: require('templates/installer'),
 
+  isStep0Disabled: function () {
+    return this.get('controller.isStepDisabled').findProperty('step',0).get('value');
+  }.property('controller.isStepDisabled.@each.value').cacheable(),
+
   isStep1Disabled: function () {
     return this.get('controller.isStepDisabled').findProperty('step',1).get('value');
   }.property('controller.isStepDisabled.@each.value').cacheable(),

+ 16 - 22
ambari-web/app/views/wizard/step1_view.js

@@ -20,29 +20,23 @@
 var App = require('app');
 
 App.WizardStep1View = Em.View.extend({
-
-  tagName: "form", //todo: why form?
   templateName: require('templates/wizard/step1'),
 
-  //todo: create property for placeholder(go to template)
-
-  didInsertElement: function () {
-    $("[rel=popover]").popover({'placement': 'right', 'trigger': 'hover'});
-    this.get('controller').loadStep();
-  },
-
-  //todo: rename it to class or write comments
-  onError: function () {
-    return this.get('controller.clusterNameError') !== '';
-  }.property('controller.clusterNameError')
-
-});
-
-App.WizardStep1ViewClusterNameInput = Em.TextField.extend({
-  keyPress: function(event) {
-    if (event.keyCode == 13) {
-      this.get('parentView.controller').submit();
-      return false;
+  stacks: function() {
+    return this.get('controller.content.stacks');
+  }.property('controller.content.stacks'),
+
+  stackRadioButton: Ember.Checkbox.extend({
+    tagName: 'input',
+    attributeBindings: ['type', 'checked'],
+    checked: function () {
+      return this.get('content.isSelected');
+    }.property('content.isSelected'),
+    type: 'radio',
+
+    click: function () {
+      this.get('parentView.stacks').setEach('isSelected', false);
+      this.get('parentView.stacks').findProperty('name', this.get('content.name')).set('isSelected', true);
     }
-  }
+  })
 });