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.
+
+
+
+
+ Setup an external database instance.
+
+ This can be a hosted solution like Amazon RDS or Google Cloud SQL,
+ Or a self-hosted instance or multi-master cluster.
+
+
+ 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}}
+
+
+
+
+ Export Database
+
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
+
+
+
+{{/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}}
+
+ Are you sure? Click again to really disable access HA
+
+ {{else}}
+
+ Disable HA
+
+ {{/if}}
+
+
{{else}}
{{#if isLocalDb}}
@@ -48,7 +83,7 @@
2. Generate HA config script
-