From b2f9e7dbdc7ab458aba0c106d6243cba01c0e230 Mon Sep 17 00:00:00 2001 From: Vincent Fiduccia Date: Tue, 29 Mar 2016 23:03:16 -0700 Subject: [PATCH 1/8] WIP: HA --- app/admin-tab/ha/controller.js | 66 +++++++++++ app/admin-tab/ha/route.js | 22 ++++ app/admin-tab/ha/template.hbs | 197 +++++++++++++++++++++++++++++++ app/router.js | 1 + app/services/settings.js | 8 +- app/templates/tabs/admin-tab.hbs | 1 + app/utils/constants.js | 30 ++--- 7 files changed, 309 insertions(+), 16 deletions(-) create mode 100644 app/admin-tab/ha/controller.js create mode 100644 app/admin-tab/ha/route.js create mode 100644 app/admin-tab/ha/template.hbs diff --git a/app/admin-tab/ha/controller.js b/app/admin-tab/ha/controller.js new file mode 100644 index 000000000..9a0847760 --- /dev/null +++ b/app/admin-tab/ha/controller.js @@ -0,0 +1,66 @@ +import Ember from 'ember'; +import Util from 'ui/utils/util'; + +export default Ember.Controller.extend({ + settings: Ember.inject.service(), + growl: Ember.inject.service(), + + userUrl: '', + selfSign: false, + + isLocalDb: function() { + return (this.get('model.dbHost')||'').toLowerCase() === 'localhost'; + }.property('model.haConfig.dbHost'), + + actions: { + exportDatabase() { + Util.download(this.get('model.haConfig').linkFor('dbdump')); + }, + + readFile(field, text) { + this.set('model.createScript.'+field, text.trim()); + }, + + generateConfig() { + var ha = this.get('model.haConfig'); + var cs = this.get('model.createScript'); + cs.doAction('createscript', cs).then(() => { + ha.save().then(() => { + }); + }).catch((err) => { + this.get('growl').fromError(err); + }); + }, + }, + + setup: (function() { + this.set('userUrl', this.get('model.hostRegistrationUrl')); + }).on('init'), + + userUrlChanged: function() { + let val = this.get('userUrl')||''; + let match = val.match(/^https?:\/\//); + if ( match ) + { + val = val.substr(match[0].length); + } + + if ( match = val.match(/^(.*):(\d+)$/) ) + { + let port = parseInt(match[2],10); + if ( port > 0 ) + { + this.set('model.httpsPort', port); + } + } + + let pos = val.indexOf('/',1); + if ( pos >= 1 && val.substr(pos-2,2) !== ':/' && val.substr(pos-2,2) !== 's:' ) + { + val = val.substr(0,pos); + } + + this.set('userUrl', val); + + }.observes('userUrl'), +}); diff --git a/app/admin-tab/ha/route.js b/app/admin-tab/ha/route.js new file mode 100644 index 000000000..c8ee4c41a --- /dev/null +++ b/app/admin-tab/ha/route.js @@ -0,0 +1,22 @@ +import Ember from 'ember'; + +export default Ember.Route.extend({ + settings: Ember.inject.service(), + + beforeModel() { + return Ember.RSVP.all([ + this.get('store').find('schema','haconfig', {authAsUser: true}), + this.get('store').find('schema','haconfiginput', {authAsUser: true}), + ]); + }, + + model() { + var store = this.get('store'); + return store.find('haConfig', null, {authAsUser: true, forceReload: true}).then((res) => { + return Ember.Object.create({ + haConfig: res.objectAt(0), + createScript: store.createRecord({type: 'haConfigInput'}) + }); + }); + } +}); diff --git a/app/admin-tab/ha/template.hbs b/app/admin-tab/ha/template.hbs new file mode 100644 index 000000000..b43b4e1be --- /dev/null +++ b/app/admin-tab/ha/template.hbs @@ -0,0 +1,197 @@ +
+

High Availability is {{#if model.haConfig.enabled}}enabled{{else}}not configured{{/if}}

+
+ +{{#if model.haConfig.enabled}} + Info about your HA setup +{{else}} + + {{#if isLocalDb}} +
+

1. Setup an external database

+
+

+ This {{settings.appName}} installation is currently configured to use the built-in database server, but HA requires a standalone installation of MySQL. +

+

+

+

+

+ +

Uncompressed Size: {{format-mib model.haConfig.dbSize}}

+

+
+ {{else}} +
+

1. Setup an external database

+

+ Complete, {{model.haConfig.dbHost}} will be used as the external database for HA. +

+
+ {{/if}} + +
+

2. Generate HA config script

+
+
+
+
+ +
+
+
+ +
+
+ +
+
+ +
+
+
+ +
+
+ +
+
+
+ + https:// + + {{input type="text" value=userUrl type="text" classNames="form-control"}} +
+

This should be a FQDN that resolves to the addresses of or is a load balancer for {{#if (eq model.createScript.clusterSize 1)}}the HA host{{else}}all {{model.createScript.clusterSize}} HA hosts{{/if}}. Do not include /v1 or any other path.

+
+
+ +
+
+ +
+
+
+ +
+
+ +
+
+
+ + {{#liquid-if (not selfSign)}} +
+
+
+ + {{read-text-file accept="text/*, .pem, .pkey, .key" action=(action "readFile" "key")}} +
+ {{textarea value=model.createScript.key classNames="form-control no-resize" rows="5" placeholder="Paste in the private key, starting with -----BEGIN RSA PRIVATE KEY-----"}} +
+ +
+
+ + {{read-text-file accept="text/*, .pem, .crt" action=(action "readFile" "cert")}} +
+ {{textarea value=model.createScript.cert classNames="form-control no-resize" rows="5" placeholder="Paste in the primary certificate, starting with -----BEGIN CERTIFICATE-----"}} +
+ +
+
+ + {{read-text-file accept="text/*, .pem, .crt" action=(action "readFile" "certChain")}} +
+ {{textarea value=model.createScript.certChain classNames="form-control no-resize" rows="5" placeholder="Optional; Paste in the additional chained certificates, starting with -----BEGIN CERTIFICATE-----"}} +
+
+ {{/liquid-if}} + +
+
+ +
+
+ + + + + + + + + + + + + + + + + + +
HTTPSRequired{{input class="form-control input-sm r-mb5" type="number" min=1 max=65535 value=model.createScript.httpsPort}}
HTTP{{input class="form-control input-sm r-mb5" type="number" min=1 max=65535 value=model.createScript.httpPort disabled=(not model.createScript.httpEnabled)}}
Swarm{{input class="form-control input-sm r-mb5" type="number" min=1 max=65535 value=model.createScript.swarmPort disabled=(not model.createScript.swarmEnabled)}}
+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
RedisRequired{{input class="form-control input-sm r-mb5" type="number" min=1 max=65535 value=model.createScript.redisPort}}
ZooKeeper ClientRequired{{input class="form-control input-sm r-mb5" type="number" min=1 max=65535 value=model.createScript.zookeeperClient}}
ZooKeeper QuorumRequired{{input class="form-control input-sm r-mb5" type="number" min=1 max=65535 value=model.createScript.zookeeperQuorum}}
ZooKeeper LeaderRequired{{input class="form-control input-sm r-mb5" type="number" min=1 max=65535 value=model.createScript.zookeeperLeader}}
+
+
+ +

+ +

+
+
+{{/if}} diff --git a/app/router.js b/app/router.js index 95ff6b3e1..3b89e2987 100644 --- a/app/router.js +++ b/app/router.js @@ -47,6 +47,7 @@ Router.map(function() { }); this.route('audit-logs'); + this.route('ha'); }); this.route('project', {path: '/env/:project_id'}, function() { diff --git a/app/services/settings.js b/app/services/settings.js index 83dbc15ad..bd66f0230 100644 --- a/app/services/settings.js +++ b/app/services/settings.js @@ -6,7 +6,7 @@ export function normalizeName(str) { } export function denormalizeName(str) { - return str.replace(C.SETTING.DOT_CHAR,'.').toLowerCase(); + return str.replace(new RegExp('['+C.SETTING.DOT_CHAR+']','g'),'.').toLowerCase(); } export default Ember.Service.extend(Ember.Evented, { @@ -85,6 +85,12 @@ export default Ember.Service.extend(Ember.Evented, { return this.get('asMap')[normalizeName(name)]; }, + findAsUser(key) { + return this.get('store').find('setting', denormalizeName(key), {authAsUser: true, forceReload: true}).then(() => { + return Ember.RSVP.resolve(this.unknownProperty(key)); + }); + }, + asMap: function() { var out = {}; (this.get('all')||[]).forEach((setting) => { diff --git a/app/templates/tabs/admin-tab.hbs b/app/templates/tabs/admin-tab.hbs index ff08a9566..731f852f9 100644 --- a/app/templates/tabs/admin-tab.hbs +++ b/app/templates/tabs/admin-tab.hbs @@ -3,3 +3,4 @@ {{#link-to "admin-tab.accounts"}}Accounts{{/link-to}} {{#link-to "admin-tab.auth"}}Access Control{{/link-to}} {{#link-to "admin-tab.settings"}}Settings{{/link-to}} +{{#link-to "admin-tab.ha"}}HA{{/link-to}} diff --git a/app/utils/constants.js b/app/utils/constants.js index 8445e34f3..d1e7bf784 100644 --- a/app/utils/constants.js +++ b/app/utils/constants.js @@ -166,23 +166,23 @@ var C = { SETTING: { // Dots in key names do not mix well with Ember, so use $ in their place. - DOT_CHAR: '$', - VERSION_RANCHER: 'rancher$server$image', - VERSION_COMPOSE: 'rancher$compose$version', - VERSION_CATTLE: 'cattle$version', - VERSION_MACHINE: 'docker$machine$version', - VERSION_GMS: 'go$machine$service$version', + DOT_CHAR: '$', + VERSION_RANCHER: 'rancher$server$image', + VERSION_COMPOSE: 'rancher$compose$version', + VERSION_CATTLE: 'cattle$version', + VERSION_MACHINE: 'docker$machine$version', + VERSION_GMS: 'go$machine$service$version', COMPOSE_URL: { - DARWIN: 'rancher$compose$darwin$url', - WINDOWS: 'rancher$compose$windows$url', - LINUX: 'rancher$compose$linux$url', + DARWIN: 'rancher$compose$darwin$url', + WINDOWS: 'rancher$compose$windows$url', + LINUX: 'rancher$compose$linux$url', }, - API_HOST: 'api$host', - CATALOG_URL: 'catalog$url', - VM_ENABLED: 'vm$enabled', - HELP_ENABLED: 'help$enabled', - SWARM_PORT: 'swarm$tls$port', - ENGINE_URL: 'engine$install$url' + API_HOST: 'api$host', + CATALOG_URL: 'catalog$url', + VM_ENABLED: 'vm$enabled', + HELP_ENABLED: 'help$enabled', + SWARM_PORT: 'swarm$tls$port', + ENGINE_URL: 'engine$install$url', }, USER: { From 4ed38e21cc03e239e5a67a06cac44f283f3d0ad1 Mon Sep 17 00:00:00 2001 From: Vincent Fiduccia Date: Wed, 30 Mar 2016 00:15:22 -0700 Subject: [PATCH 2/8] Enable, disable, and show config script --- app/admin-tab/ha/controller.js | 80 ++++++++++++++++++++++++++++------ app/admin-tab/ha/template.hbs | 60 ++++++++++++++++++++++--- 2 files changed, 120 insertions(+), 20 deletions(-) diff --git a/app/admin-tab/ha/controller.js b/app/admin-tab/ha/controller.js index 9a0847760..4c39e072c 100644 --- a/app/admin-tab/ha/controller.js +++ b/app/admin-tab/ha/controller.js @@ -1,15 +1,21 @@ import Ember from 'ember'; import Util from 'ui/utils/util'; +import C from 'ui/utils/constants'; export default Ember.Controller.extend({ settings: Ember.inject.service(), growl: Ember.inject.service(), userUrl: '', - selfSign: false, + selfSign: true, + generating: false, + justGenerated: false, + configScript: null, + errors: null, + confirmPanic: false, isLocalDb: function() { - return (this.get('model.dbHost')||'').toLowerCase() === 'localhost'; + return (this.get('model.haConfig.dbHost')||'').toLowerCase() === 'localhost'; }.property('model.haConfig.dbHost'), actions: { @@ -21,21 +27,52 @@ export default Ember.Controller.extend({ this.set('model.createScript.'+field, text.trim()); }, - generateConfig() { - var ha = this.get('model.haConfig'); - var cs = this.get('model.createScript'); - cs.doAction('createscript', cs).then(() => { - ha.save().then(() => { - }); + promptPanic() { + this.set('confirmPanic', true); + Ember.run.later(() => { + if ( this._state !== 'destroying' ) + { + this.set('confirmPanic', false) + } + }, 5000); + }, + + panic() { + var orig = this.get('model.haConfig'); + var clone = orig.clone(); + clone.set('enabled', false); + clone.save({headers: {[C.HEADER.PROJECT]: undefined}}).then(() => { + orig.set('enabled', false); }).catch((err) => { this.get('growl').fromError(err); }); }, - }, - setup: (function() { - this.set('userUrl', this.get('model.hostRegistrationUrl')); - }).on('init'), + generateConfig() { + var ha = this.get('model.haConfig'); + var cs = this.get('model.createScript'); + + if ( !this.validate() ) { + return; + } + + this.set('generating',true); + + ha.doAction('createscript', cs, {headers: {[C.HEADER.PROJECT]: undefined}}).then((script) => { + var clone = ha.clone(); + clone.set('enabled',true); + clone.save({headers: {[C.HEADER.PROJECT]: undefined}}).then(() => { + ha.set('enabled', true); + this.set('justGenerated',true); + this.set('configScript', script); + }); + }).catch((err) => { + this.get('growl').fromError(err); + }).finally(() => { + this.set('generating', false); + }); + }, + }, userUrlChanged: function() { let val = this.get('userUrl')||''; @@ -61,6 +98,23 @@ export default Ember.Controller.extend({ } this.set('userUrl', val); - + this.set('model.createScript.hostRegistrationUrl', val); }.observes('userUrl'), + + selfSignChanged: function() { + if ( this.get('selfSign') ) + { + this.get('model.createScript').setProperties({ + key: null, + cert: null, + certChain: null, + }); + } + }.observes('selfSign'), + + validate() { + var errors = this.get('model.createScript').validationErrors(); + this.set('errors',errors); + return errors.length === 0; + }, }); diff --git a/app/admin-tab/ha/template.hbs b/app/admin-tab/ha/template.hbs index b43b4e1be..9afba164f 100644 --- a/app/admin-tab/ha/template.hbs +++ b/app/admin-tab/ha/template.hbs @@ -1,9 +1,44 @@
-

High Availability is {{#if model.haConfig.enabled}}enabled{{else}}not configured{{/if}}

+

High Availability is {{#if model.haConfig.enabled}}enabled{{else}}not configured{{/if}}

{{#if model.haConfig.enabled}} - Info about your HA setup + {{#if justGenerated}} +
+

3. Add hosts

+
+

+ Copy this script that was each host: {{copy-to-clipboard clipboardText=configScript size="small"}} +

{{configScript}}
+

+ +

+ Then run it on each host to register them: +

bash ./awesome-script.sh rancher/server:{{if rancherVersion rancherVersion 'latest'}}
+

+
+ {{/if}} + +
+

Info about your HA setup and it's awesomeness.

+
+ +
+

Danger Zone™

+
+ +

+ {{#if confirmPanic}} + + {{else}} + + {{/if}} +

+
{{else}} {{#if isLocalDb}} @@ -48,7 +83,7 @@

2. Generate HA config script


-
+
@@ -172,17 +207,17 @@ ZooKeeper Client Required - {{input class="form-control input-sm r-mb5" type="number" min=1 max=65535 value=model.createScript.zookeeperClient}} + {{input class="form-control input-sm r-mb5" type="number" min=1 max=65535 value=model.createScript.zookeeperClientPort}} ZooKeeper Quorum Required - {{input class="form-control input-sm r-mb5" type="number" min=1 max=65535 value=model.createScript.zookeeperQuorum}} + {{input class="form-control input-sm r-mb5" type="number" min=1 max=65535 value=model.createScript.zookeeperQuorumPort}} ZooKeeper Leader Required - {{input class="form-control input-sm r-mb5" type="number" min=1 max=65535 value=model.createScript.zookeeperLeader}} + {{input class="form-control input-sm r-mb5" type="number" min=1 max=65535 value=model.createScript.zookeeperLeaderPort}} @@ -190,7 +225,18 @@

- + {{top-errors errors=errors}} + + {{#if (and false isLocalDb)}} + +

You must migrate to an external database before you can enable HA

+ {{else}} + {{#if generating}} + + {{else}} + + {{/if}} + {{/if}}

From 39692563726d20716078f4191cbbc71351e4c8c2 Mon Sep 17 00:00:00 2001 From: Vincent Fiduccia Date: Wed, 30 Mar 2016 00:22:08 -0700 Subject: [PATCH 3/8] Show scripts as code blocks --- app/admin-tab/ha/controller.js | 6 +++++- app/admin-tab/ha/template.hbs | 4 ++-- app/components/code-block/component.js | 5 +++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/app/admin-tab/ha/controller.js b/app/admin-tab/ha/controller.js index 4c39e072c..959a24d2a 100644 --- a/app/admin-tab/ha/controller.js +++ b/app/admin-tab/ha/controller.js @@ -14,6 +14,10 @@ export default Ember.Controller.extend({ errors: null, confirmPanic: false, + configExecute: function() { + return 'bash ./awesome-script.sh rancher/server:' + (this.get('settings.rancherVersion') || 'latest'); + }.property('settings.rancherVersion'), + isLocalDb: function() { return (this.get('model.haConfig.dbHost')||'').toLowerCase() === 'localhost'; }.property('model.haConfig.dbHost'), @@ -64,7 +68,7 @@ export default Ember.Controller.extend({ clone.save({headers: {[C.HEADER.PROJECT]: undefined}}).then(() => { ha.set('enabled', true); this.set('justGenerated',true); - this.set('configScript', script); + this.set('configScript', script.trim()); }); }).catch((err) => { this.get('growl').fromError(err); diff --git a/app/admin-tab/ha/template.hbs b/app/admin-tab/ha/template.hbs index 9afba164f..393cded3c 100644 --- a/app/admin-tab/ha/template.hbs +++ b/app/admin-tab/ha/template.hbs @@ -9,12 +9,12 @@

Copy this script that was each host: {{copy-to-clipboard clipboardText=configScript size="small"}} -

{{configScript}}
+ {{code-block code=configScript language="bash" constrained=false}}

Then run it on each host to register them: -

bash ./awesome-script.sh rancher/server:{{if rancherVersion rancherVersion 'latest'}}
+ {{code-block code=configExecute language="bash" constrained=false}}

{{/if}} diff --git a/app/components/code-block/component.js b/app/components/code-block/component.js index 273c77ad9..e680878e2 100644 --- a/app/components/code-block/component.js +++ b/app/components/code-block/component.js @@ -4,10 +4,11 @@ export default Ember.Component.extend({ language: 'javascript', code: '', hide: false, + constrained: true, tagName: 'PRE', - classNames: ['line-numbers','constrained'], - classNameBindings: ['languageClass','hide:hide'], + classNames: ['line-numbers'], + classNameBindings: ['languageClass','hide:hide','constrained:constrained'], languageClass: function() { var lang = this.get('language'); From 1381e6972b93282b4c6fe744a04a10244f04083a Mon Sep 17 00:00:00 2001 From: Vincent Fiduccia Date: Wed, 30 Mar 2016 09:23:13 -0700 Subject: [PATCH 4/8] Image UUID --- app/admin-tab/ha/controller.js | 2 +- app/admin-tab/ha/template.hbs | 2 +- app/templates/service-addtl-info.hbs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/admin-tab/ha/controller.js b/app/admin-tab/ha/controller.js index 959a24d2a..fc1240193 100644 --- a/app/admin-tab/ha/controller.js +++ b/app/admin-tab/ha/controller.js @@ -36,7 +36,7 @@ export default Ember.Controller.extend({ Ember.run.later(() => { if ( this._state !== 'destroying' ) { - this.set('confirmPanic', false) + this.set('confirmPanic', false); } }, 5000); }, diff --git a/app/admin-tab/ha/template.hbs b/app/admin-tab/ha/template.hbs index 393cded3c..350e38535 100644 --- a/app/admin-tab/ha/template.hbs +++ b/app/admin-tab/ha/template.hbs @@ -8,7 +8,7 @@

3. Add hosts


- Copy this script that was each host: {{copy-to-clipboard clipboardText=configScript size="small"}} + Copy this script to each host: {{copy-to-clipboard clipboardText=configScript size="small"}} {{code-block code=configScript language="bash" constrained=false}}

diff --git a/app/templates/service-addtl-info.hbs b/app/templates/service-addtl-info.hbs index 350bf0480..29e7175b6 100644 --- a/app/templates/service-addtl-info.hbs +++ b/app/templates/service-addtl-info.hbs @@ -11,7 +11,7 @@
- {{service.launchConfig.imageUuid}} + {{service.launchConfig.displayImage}}
From eee08c640117c575ff5638d112a37de84c86f3e5 Mon Sep 17 00:00:00 2001 From: Vincent Fiduccia Date: Tue, 5 Apr 2016 23:58:52 -0700 Subject: [PATCH 5/8] Some info after enabled, download config --- app/admin-tab/ha/controller.js | 72 ++++++++++++++++++++++++++++++++-- app/admin-tab/ha/route.js | 5 +++ app/admin-tab/ha/template.hbs | 57 ++++++++++++++++++--------- app/authenticated/route.js | 6 ++- server/proxies/api.js | 1 + 5 files changed, 116 insertions(+), 25 deletions(-) diff --git a/app/admin-tab/ha/controller.js b/app/admin-tab/ha/controller.js index fc1240193..2c87a7624 100644 --- a/app/admin-tab/ha/controller.js +++ b/app/admin-tab/ha/controller.js @@ -5,6 +5,10 @@ import C from 'ui/utils/constants'; export default Ember.Controller.extend({ settings: Ember.inject.service(), growl: Ember.inject.service(), + cookies: Ember.inject.service(), + projects: Ember.inject.service(), + + csrf: Ember.computed.alias('cookies.CSRF'), userUrl: '', selfSign: true, @@ -13,9 +17,10 @@ export default Ember.Controller.extend({ configScript: null, errors: null, confirmPanic: false, + haProject: null, configExecute: function() { - return 'bash ./awesome-script.sh rancher/server:' + (this.get('settings.rancherVersion') || 'latest'); + return 'bash ./rancher-ha.sh rancher/server:' + (this.get('settings.rancherVersion') || 'latest'); }.property('settings.rancherVersion'), isLocalDb: function() { @@ -53,15 +58,18 @@ export default Ember.Controller.extend({ }, generateConfig() { - var ha = this.get('model.haConfig'); - var cs = this.get('model.createScript'); - if ( !this.validate() ) { return; } this.set('generating',true); + this.set('haProject', null); + var form = $('#haConfigForm')[0]; + form.submit(); + + var ha = this.get('model.haConfig'); + var cs = this.get('model.createScript'); ha.doAction('createscript', cs, {headers: {[C.HEADER.PROJECT]: undefined}}).then((script) => { var clone = ha.clone(); clone.set('enabled',true); @@ -69,6 +77,7 @@ export default Ember.Controller.extend({ ha.set('enabled', true); this.set('justGenerated',true); this.set('configScript', script.trim()); + this.findProject(); }); }).catch((err) => { this.get('growl').fromError(err); @@ -121,4 +130,59 @@ export default Ember.Controller.extend({ this.set('errors',errors); return errors.length === 0; }, + + findProject: function() { + this.get('store').find('project', null, {authAsUser: true, filter: {all: true}, forceReload: true}).then((projects) => { + var matches = projects.filter((project) => { + return project.get('uuid').match(/^system-management-(\d+)$/); + }); + + if ( matches.length ) + { + this.set('haProject', matches.objectAt(0)); + if ( this.get('projects.current.id') !== this.get('haProject.id') ) + { + this.send('switchProject', this.get('haProject.id'), false); + } + } + else + { + Ember.run.later(this,'findProject', 5000); + } + }); + }, + + hosts: null, + getHosts: function() { + return this.get('store').findAll('host').then((hosts) => { + this.set('hosts', hosts); + }); + }.observes('haProject'), + + hostBlurb: function() { + var total = this.get('hosts.length'); + if ( total ) + { + var active = this.get('hosts').filterBy('state','active').get('length'); + if ( active < total ) + { + return active + '/' + total; + } + else + { + return total; + } + } + else + { + return '0'; + } + }.property('hosts.@each.state'), + + cert: null, + getCertificate: function() { + return this.get('store').find('certificate', null, {filter: {name: 'system-ssl'}}).then((certs) => { + this.set('cert', certs.objectAt(0)); + }); + }.observes('haProject'), }); diff --git a/app/admin-tab/ha/route.js b/app/admin-tab/ha/route.js index c8ee4c41a..91007d8ad 100644 --- a/app/admin-tab/ha/route.js +++ b/app/admin-tab/ha/route.js @@ -18,5 +18,10 @@ export default Ember.Route.extend({ createScript: store.createRecord({type: 'haConfigInput'}) }); }); + }, + + setupController(controller/*, model*/) { + this._super(...arguments); + controller.findProject(); } }); diff --git a/app/admin-tab/ha/template.hbs b/app/admin-tab/ha/template.hbs index 350e38535..7af187d3a 100644 --- a/app/admin-tab/ha/template.hbs +++ b/app/admin-tab/ha/template.hbs @@ -8,20 +8,34 @@

3. Add hosts


- Copy this script to each host: {{copy-to-clipboard clipboardText=configScript size="small"}} - {{code-block code=configScript language="bash" constrained=false}} -

- -

- Then run it on each host to register them: + Run the script that just downloaded on each host to register them (or {{copy-to-clipboard tooltipText="" buttonText="copy it to clipboard" clipboardText=configScript size="small"}}): {{code-block code=configExecute language="bash" constrained=false}}

{{/if}} -
-

Info about your HA setup and it's awesomeness.

-
+
+
+
+ {{#if haProject}} +

Active Hosts:

+

{{hostBlurb}}

+ {{else}} + Waiting for a host... + {{/if}} +
+
+
+
+

Management Server Certificate: {{#if cert.cert}}{{copy-to-clipboard size="small" clipboardText=cert.cert}}{{/if}}

+ {{#if cert.cert}} +
{{cert.cert}}
+ {{else}} + Loading... + {{/if}} +
+
+

Danger Zone™

@@ -83,7 +97,10 @@

2. Generate HA config script


-
+ + + +
@@ -181,17 +198,19 @@ HTTPS Required - {{input class="form-control input-sm r-mb5" type="number" min=1 max=65535 value=model.createScript.httpsPort}} + {{input name="httpsPort" class="form-control input-sm r-mb5" type="number" min=1 max=65535 value=model.createScript.httpsPort}} HTTP - - {{input class="form-control input-sm r-mb5" type="number" min=1 max=65535 value=model.createScript.httpPort disabled=(not model.createScript.httpEnabled)}} + + Required + {{input name="httpPort" class="form-control input-sm r-mb5" type="number" min=1 max=65535 value=model.createScript.httpPort disabled=(not model.createScript.httpEnabled)}} Swarm - - {{input class="form-control input-sm r-mb5" type="number" min=1 max=65535 value=model.createScript.swarmPort disabled=(not model.createScript.swarmEnabled)}} + + Required + {{input name="swarmPort" class="form-control input-sm r-mb5" type="number" min=1 max=65535 value=model.createScript.swarmPort disabled=(not model.createScript.swarmEnabled)}} @@ -202,22 +221,22 @@ Redis Required - {{input class="form-control input-sm r-mb5" type="number" min=1 max=65535 value=model.createScript.redisPort}} + {{input name="redisPort" class="form-control input-sm r-mb5" type="number" min=1 max=65535 value=model.createScript.redisPort}} ZooKeeper Client Required - {{input class="form-control input-sm r-mb5" type="number" min=1 max=65535 value=model.createScript.zookeeperClientPort}} + {{input name="zookeeperClientPort" class="form-control input-sm r-mb5" type="number" min=1 max=65535 value=model.createScript.zookeeperClientPort}} ZooKeeper Quorum Required - {{input class="form-control input-sm r-mb5" type="number" min=1 max=65535 value=model.createScript.zookeeperQuorumPort}} + {{input name="zookeeperQuorumPort" class="form-control input-sm r-mb5" type="number" min=1 max=65535 value=model.createScript.zookeeperQuorumPort}} ZooKeeper Leader Required - {{input class="form-control input-sm r-mb5" type="number" min=1 max=65535 value=model.createScript.zookeeperLeaderPort}} + {{input name="zookeeperLeaderPort" class="form-control input-sm r-mb5" type="number" min=1 max=65535 value=model.createScript.zookeeperLeaderPort}} diff --git a/app/authenticated/route.js b/app/authenticated/route.js index 347741831..16fde2c76 100644 --- a/app/authenticated/route.js +++ b/app/authenticated/route.js @@ -205,9 +205,11 @@ export default Ember.Route.extend(Subscribe, { this.controllerFor('application').set('showAbout', true); }, - switchProject(projectId) { + switchProject(projectId, transition=true) { this.reset(); - this.intermediateTransitionTo('authenticated'); + if ( transition ) { + this.intermediateTransitionTo('authenticated'); + } this.set(`tab-session.${C.TABSESSION.PROJECT}`, projectId); this.refresh(); }, diff --git a/server/proxies/api.js b/server/proxies/api.js index 5732e9007..f76862a29 100644 --- a/server/proxies/api.js +++ b/server/proxies/api.js @@ -10,6 +10,7 @@ module.exports = function(app, options) { ws: true, xfwd: false, target: config.apiServer, + secure: false, }); proxy.on('error', onProxyError); From 0a08243522bede996ff64dc9531d3aa22411e667 Mon Sep 17 00:00:00 2001 From: Vincent Fiduccia Date: Wed, 6 Apr 2016 09:38:30 -0700 Subject: [PATCH 6/8] Send https:// --- app/admin-tab/ha/controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/admin-tab/ha/controller.js b/app/admin-tab/ha/controller.js index 2c87a7624..c0f3d883f 100644 --- a/app/admin-tab/ha/controller.js +++ b/app/admin-tab/ha/controller.js @@ -111,7 +111,7 @@ export default Ember.Controller.extend({ } this.set('userUrl', val); - this.set('model.createScript.hostRegistrationUrl', val); + this.set('model.createScript.hostRegistrationUrl', 'https://'+val); }.observes('userUrl'), selfSignChanged: function() { From 6c09b125b6bd22c919491052eed4bfe1615661ea Mon Sep 17 00:00:00 2001 From: Vincent Fiduccia Date: Wed, 6 Apr 2016 17:30:09 -0700 Subject: [PATCH 7/8] More HA --- app/admin-tab/ha/controller.js | 60 ++++++++-------- app/admin-tab/ha/template.hbs | 126 ++++++++++++++++++++++----------- 2 files changed, 118 insertions(+), 68 deletions(-) diff --git a/app/admin-tab/ha/controller.js b/app/admin-tab/ha/controller.js index c0f3d883f..b6f82781c 100644 --- a/app/admin-tab/ha/controller.js +++ b/app/admin-tab/ha/controller.js @@ -14,7 +14,6 @@ export default Ember.Controller.extend({ selfSign: true, generating: false, justGenerated: false, - configScript: null, errors: null, confirmPanic: false, haProject: null, @@ -23,6 +22,18 @@ export default Ember.Controller.extend({ return 'bash ./rancher-ha.sh rancher/server:' + (this.get('settings.rancherVersion') || 'latest'); }.property('settings.rancherVersion'), + runCode: function() { + let version = this.get('settings.rancherVersion') || 'latest'; + + return `sudo docker run -d --restart=always -p 8080:8080 \\ +-e CATTLE_DB_CATTLE_MYSQL_HOST= \\ +-e CATTLE_DB_CATTLE_MYSQL_PORT= \\ +-e CATTLE_DB_CATTLE_MYSQL_NAME= \\ +-e CATTLE_DB_CATTLE_USERNAME= \\ +-e CATTLE_DB_CATTLE_PASSWORD= \\ +rancher/server:${version}`; + }.property('settings.rancherVersion'), + isLocalDb: function() { return (this.get('model.haConfig.dbHost')||'').toLowerCase() === 'localhost'; }.property('model.haConfig.dbHost'), @@ -65,24 +76,23 @@ export default Ember.Controller.extend({ this.set('generating',true); this.set('haProject', null); + Ember.run.later(() => { + this.set('generating',false); + this.set('justGenerated',true); + }, 500); + }, + + downloadConfig() { var form = $('#haConfigForm')[0]; form.submit(); + this.set('downloaded',true); var ha = this.get('model.haConfig'); - var cs = this.get('model.createScript'); - ha.doAction('createscript', cs, {headers: {[C.HEADER.PROJECT]: undefined}}).then((script) => { - var clone = ha.clone(); - clone.set('enabled',true); - clone.save({headers: {[C.HEADER.PROJECT]: undefined}}).then(() => { - ha.set('enabled', true); - this.set('justGenerated',true); - this.set('configScript', script.trim()); - this.findProject(); - }); - }).catch((err) => { - this.get('growl').fromError(err); - }).finally(() => { - this.set('generating', false); + var clone = ha.clone(); + clone.set('enabled',true); + clone.save({headers: {[C.HEADER.PROJECT]: undefined}}).then((neu) => { + ha.merge(neu); + this.findProject(); }); }, }, @@ -160,24 +170,18 @@ export default Ember.Controller.extend({ }.observes('haProject'), hostBlurb: function() { - var total = this.get('hosts.length'); - if ( total ) + var total = this.get('model.haConfig.clusterSize'); + var active = (this.get('hosts')||[]).filterBy('state','active').get('length'); + + if ( active < total ) { - var active = this.get('hosts').filterBy('state','active').get('length'); - if ( active < total ) - { - return active + '/' + total; - } - else - { - return total; - } + return active + '/' + total; } else { - return '0'; + return total; } - }.property('hosts.@each.state'), + }.property('hosts.@each.state','model.haConfig.clusterSize'), cert: null, getCertificate: function() { diff --git a/app/admin-tab/ha/template.hbs b/app/admin-tab/ha/template.hbs index 7af187d3a..a14f665a3 100644 --- a/app/admin-tab/ha/template.hbs +++ b/app/admin-tab/ha/template.hbs @@ -3,22 +3,20 @@
{{#if model.haConfig.enabled}} - {{#if justGenerated}} -
-

3. Add hosts

-
-

- Run the script that just downloaded on each host to register them (or {{copy-to-clipboard tooltipText="" buttonText="copy it to clipboard" clipboardText=configScript size="small"}}): - {{code-block code=configExecute language="bash" constrained=false}} -

-
- {{/if}} +
+

5. Add hosts

+
+

+ Copy the downloaded script and run it on to each HA host to register them: + {{code-block code=configExecute language="bash" constrained=false}} +

+
+

Hosts:

{{#if haProject}} -

Active Hosts:

{{hostBlurb}}

{{else}} Waiting for a host... @@ -31,7 +29,7 @@ {{#if cert.cert}}
{{cert.cert}}
{{else}} - Loading... + Waiting for a host... {{/if}}
@@ -55,8 +53,8 @@
{{else}} - {{#if isLocalDb}} -
+
+ {{#if isLocalDb}}

1. Setup an external database


@@ -73,32 +71,58 @@

  • Click the export button below to export the entire contents of the current database.
  • Import the data into the new external database.
  • -
  • Re-launch your current server container pointed at the external database, then come back here. - {{#unless settings.isPrivateLabel}} - See docs for more info. - {{/unless}} -
  • Uncompressed Size: {{format-mib model.haConfig.dbSize}}

    -
    - {{else}} -
    + {{else}}

    1. Setup an external database

    -

    - Complete, {{model.haConfig.dbHost}} will be used as the external database for HA. +

    + Complete, running off of an external database.

    -
    - {{/if}} + {{/if}} +
    -

    2. Generate HA config script

    -
    + {{#if isLocalDb}} +

    2. Use the new external database

    +
    +
      +
    • Re-launch the server container pointed at the external database: + {{code-block code=runCode language="bash" constrained=false}} +
    • + {{#unless settings.isPrivateLabel}} +
    • + See docs for more detail. +
    • + {{/unless}} +
    + {{else}} +

    2. Use the new external database

    +

    + Complete, {{model.haConfig.dbHost}} will be used as the external database for HA. +

    + {{/if}} +
    + +
    + {{#if justGenerated}} +

    3. Generate HA config script

    +

    + Complete. +

    + {{else}} +

    3. Generate HA config script

    +
    + {{#if isLocalDb}} +

    Come back here once you are running off the external database...

    + {{/if}} + {{/if}} + - +
    @@ -167,7 +191,7 @@ {{read-text-file accept="text/*, .pem, .pkey, .key" action=(action "readFile" "key")}}
    - {{textarea value=model.createScript.key classNames="form-control no-resize" rows="5" placeholder="Paste in the private key, starting with -----BEGIN RSA PRIVATE KEY-----"}} + {{textarea name="key" value=model.createScript.key classNames="form-control no-resize" rows="5" placeholder="Paste in the private key, starting with -----BEGIN RSA PRIVATE KEY-----"}}
    @@ -175,7 +199,7 @@ {{read-text-file accept="text/*, .pem, .crt" action=(action "readFile" "cert")}}
    - {{textarea value=model.createScript.cert classNames="form-control no-resize" rows="5" placeholder="Paste in the primary certificate, starting with -----BEGIN CERTIFICATE-----"}} + {{textarea name="cert" value=model.createScript.cert classNames="form-control no-resize" rows="5" placeholder="Paste in the primary certificate, starting with -----BEGIN CERTIFICATE-----"}}
    @@ -183,7 +207,7 @@ {{read-text-file accept="text/*, .pem, .crt" action=(action "readFile" "certChain")}}
    - {{textarea value=model.createScript.certChain classNames="form-control no-resize" rows="5" placeholder="Optional; Paste in the additional chained certificates, starting with -----BEGIN CERTIFICATE-----"}} + {{textarea name="certChain" value=model.createScript.certChain classNames="form-control no-resize" rows="5" placeholder="Optional; Paste in the additional chained certificates, starting with -----BEGIN CERTIFICATE-----"}} {{/liquid-if}} @@ -246,17 +270,39 @@

    {{top-errors errors=errors}} - {{#if (and false isLocalDb)}} - -

    You must migrate to an external database before you can enable HA

    + {{#if generating}} + {{else}} - {{#if generating}} - - {{else}} - - {{/if}} + {{/if}}

    + +
    + {{#if downloaded}} +

    4. Download script

    +

    + Complete, check your Downloads folder. +

    + {{else}} +

    4. Download script

    +
    + {{#if justGenerated}} +

    + Click the button below to download a shell script. +

    + The script generates new encryption keys that will be used for the HA hosts to communicate with each other, so keep it safe. + New keys are generated every time you download the config script, and all hosts must have the same keys for HA to work. +
    +

    + + + {{else}} +

    + Generate the script in step 3. +

    + {{/if}} + {{/if}} +
    {{/if}} From 18caa0debc4083d7877371d10c103b339740f871 Mon Sep 17 00:00:00 2001 From: Vincent Fiduccia Date: Wed, 6 Apr 2016 17:52:41 -0700 Subject: [PATCH 8/8] More HA --- app/admin-tab/ha/controller.js | 26 ++++-- app/admin-tab/ha/template.hbs | 144 +++++++++++++++++---------------- package.json | 2 +- 3 files changed, 95 insertions(+), 77 deletions(-) diff --git a/app/admin-tab/ha/controller.js b/app/admin-tab/ha/controller.js index b6f82781c..b845c1125 100644 --- a/app/admin-tab/ha/controller.js +++ b/app/admin-tab/ha/controller.js @@ -19,7 +19,7 @@ export default Ember.Controller.extend({ haProject: null, configExecute: function() { - return 'bash ./rancher-ha.sh rancher/server:' + (this.get('settings.rancherVersion') || 'latest'); + return 'sudo bash ./rancher-ha.sh rancher/server:' + (this.get('settings.rancherVersion') || 'latest'); }.property('settings.rancherVersion'), runCode: function() { @@ -144,13 +144,17 @@ rancher/server:${version}`; findProject: function() { this.get('store').find('project', null, {authAsUser: true, filter: {all: true}, forceReload: true}).then((projects) => { var matches = projects.filter((project) => { - return project.get('uuid').match(/^system-management-(\d+)$/); + return project.get('uuid').match(/^system-ha-(\d+)$/) || project.get('uuid').match(/^system-management-(\d+)$/); }); if ( matches.length ) { this.set('haProject', matches.objectAt(0)); - if ( this.get('projects.current.id') !== this.get('haProject.id') ) + if ( this.get('projects.current.id') === this.get('haProject.id') ) + { + this.getHosts(); + } + else { this.send('switchProject', this.get('haProject.id'), false); } @@ -164,17 +168,26 @@ rancher/server:${version}`; hosts: null, getHosts: function() { - return this.get('store').findAll('host').then((hosts) => { + return this.get('store').findAll('host', null, {forceReload: true}).then((hosts) => { this.set('hosts', hosts); }); }.observes('haProject'), + expectedHosts: Ember.computed.alias('model.haConfig.clusterSize'), + activeHosts: function() { + return (this.get('hosts')||[]).filterBy('state','active').get('length'); + }.property('hosts.@each.state'), + hostBlurb: function() { - var total = this.get('model.haConfig.clusterSize'); - var active = (this.get('hosts')||[]).filterBy('state','active').get('length'); + clearInterval(this.get('hostTimer')); + var total = this.get('expectedHosts'); + var active = this.get('activeHosts'); if ( active < total ) { + this.set('hostTimer', setInterval(() => { + this.getHosts(); + }, 5000)); return active + '/' + total; } else @@ -187,6 +200,7 @@ rancher/server:${version}`; getCertificate: function() { return this.get('store').find('certificate', null, {filter: {name: 'system-ssl'}}).then((certs) => { this.set('cert', certs.objectAt(0)); + this.getHosts(); }); }.observes('haProject'), }); diff --git a/app/admin-tab/ha/template.hbs b/app/admin-tab/ha/template.hbs index a14f665a3..7cc4312da 100644 --- a/app/admin-tab/ha/template.hbs +++ b/app/admin-tab/ha/template.hbs @@ -3,21 +3,23 @@ {{#if model.haConfig.enabled}} -
    -

    5. Add hosts

    -
    -

    - Copy the downloaded script and run it on to each HA host to register them: - {{code-block code=configExecute language="bash" constrained=false}} -

    -
    + {{#if (lt activeHosts expectedHosts)}} +
    +

    5. Add hosts

    +
    +

    + Copy the downloaded script and run it on to each HA host to register them: + {{code-block code=configExecute language="bash" constrained=false}} +

    +
    + {{/if}}
    -

    Hosts:

    - {{#if haProject}} -

    {{hostBlurb}}

    +

    Hosts:

    + {{#if hosts}} +

    {{hostBlurb}}

    {{else}} Waiting for a host... {{/if}} @@ -25,7 +27,7 @@
    -

    Management Server Certificate: {{#if cert.cert}}{{copy-to-clipboard size="small" clipboardText=cert.cert}}{{/if}}

    +

    Management Server Certificate: {{#if cert.cert}}{{copy-to-clipboard size="small" clipboardText=cert.cert}}{{/if}}

    {{#if cert.cert}}
    {{cert.cert}}
    {{else}} @@ -122,7 +124,7 @@ {{/if}} -
    +
    @@ -132,19 +134,19 @@
    @@ -212,60 +214,62 @@
    {{/liquid-if}} -
    -
    - + {{#advanced-section}} +
    +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + +
    HTTPSRequired{{input name="httpsPort" class="form-control input-sm r-mb5" type="number" min=1 max=65535 value=model.createScript.httpsPort}}
    HTTPRequired{{input name="httpPort" class="form-control input-sm r-mb5" type="number" min=1 max=65535 value=model.createScript.httpPort disabled=(not model.createScript.httpEnabled)}}
    SwarmRequired{{input name="swarmPort" class="form-control input-sm r-mb5" type="number" min=1 max=65535 value=model.createScript.swarmPort disabled=(not model.createScript.swarmEnabled)}}
    +
    +
    + + + + + + + + + + + + + + + + + + + + + + + +
    RedisRequired{{input name="redisPort" class="form-control input-sm r-mb5" type="number" min=1 max=65535 value=model.createScript.redisPort}}
    ZooKeeper ClientRequired{{input name="zookeeperClientPort" class="form-control input-sm r-mb5" type="number" min=1 max=65535 value=model.createScript.zookeeperClientPort}}
    ZooKeeper QuorumRequired{{input name="zookeeperQuorumPort" class="form-control input-sm r-mb5" type="number" min=1 max=65535 value=model.createScript.zookeeperQuorumPort}}
    ZooKeeper LeaderRequired{{input name="zookeeperLeaderPort" class="form-control input-sm r-mb5" type="number" min=1 max=65535 value=model.createScript.zookeeperLeaderPort}}
    +
    -
    - - - - - - - - - - - - - - - - - - - - -
    HTTPSRequired{{input name="httpsPort" class="form-control input-sm r-mb5" type="number" min=1 max=65535 value=model.createScript.httpsPort}}
    HTTPRequired{{input name="httpPort" class="form-control input-sm r-mb5" type="number" min=1 max=65535 value=model.createScript.httpPort disabled=(not model.createScript.httpEnabled)}}
    SwarmRequired{{input name="swarmPort" class="form-control input-sm r-mb5" type="number" min=1 max=65535 value=model.createScript.swarmPort disabled=(not model.createScript.swarmEnabled)}}
    -
    -
    - - - - - - - - - - - - - - - - - - - - - - - -
    RedisRequired{{input name="redisPort" class="form-control input-sm r-mb5" type="number" min=1 max=65535 value=model.createScript.redisPort}}
    ZooKeeper ClientRequired{{input name="zookeeperClientPort" class="form-control input-sm r-mb5" type="number" min=1 max=65535 value=model.createScript.zookeeperClientPort}}
    ZooKeeper QuorumRequired{{input name="zookeeperQuorumPort" class="form-control input-sm r-mb5" type="number" min=1 max=65535 value=model.createScript.zookeeperQuorumPort}}
    ZooKeeper LeaderRequired{{input name="zookeeperLeaderPort" class="form-control input-sm r-mb5" type="number" min=1 max=65535 value=model.createScript.zookeeperLeaderPort}}
    -
    -
    + {{/advanced-section}}

    {{top-errors errors=errors}} @@ -291,7 +295,7 @@ {{#if justGenerated}}

    Click the button below to download a shell script. -

    +
    The script generates new encryption keys that will be used for the HA hosts to communicate with each other, so keep it safe. New keys are generated every time you download the config script, and all hosts must have the same keys for HA to work.
    diff --git a/package.json b/package.json index 43cedc0fa..f6a09be04 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ui", - "version": "0.100.4", + "version": "1.0.1", "private": true, "directories": { "doc": "doc",