From ec60004c963ab8a45bf47cd405ef41030817bee9 Mon Sep 17 00:00:00 2001 From: Guillaume Liautard Date: Thu, 14 Apr 2022 01:10:28 +0200 Subject: [PATCH] Streams Basic Load Balancing (Allows multiple Forwarding Hosts) --- .../migrations/20220413214306_stream_multi.js | 40 ++++++++++++++++ backend/models/stream.js | 12 ++++- backend/schema/endpoints/streams.json | 48 +++++++++++-------- backend/templates/stream.conf | 23 +++++++-- frontend/js/app/nginx/stream/form.ejs | 4 +- frontend/js/app/nginx/stream/form.js | 26 +++++++++- frontend/js/app/nginx/stream/list/item.ejs | 13 ++++- frontend/js/app/nginx/stream/list/main.ejs | 1 + frontend/js/models/stream.js | 2 +- 9 files changed, 137 insertions(+), 32 deletions(-) create mode 100644 backend/migrations/20220413214306_stream_multi.js diff --git a/backend/migrations/20220413214306_stream_multi.js b/backend/migrations/20220413214306_stream_multi.js new file mode 100644 index 000000000..f3edb6c90 --- /dev/null +++ b/backend/migrations/20220413214306_stream_multi.js @@ -0,0 +1,40 @@ +const migrate_name = 'identifier_for_migrate'; +const logger = require('../logger').migrate; + +/** + * Migrate + * + * @see http://knexjs.org/#Schema + * + * @param {Object} knex + * @param {Promise} Promise + * @returns {Promise} + */ +exports.up = function (knex, Promise) { + logger.info('[' + migrate_name + '] Migrating Up...'); + + return knex.schema.table('stream', (table) => { + table.renameColumn('forwarding_host', 'forwarding_hosts'); + }) + .then(function () { + logger.info('[' + migrate_name + '] stream Table altered'); + }); +}; + +/** + * Undo Migrate + * + * @param {Object} knex + * @param {Promise} Promise + * @returns {Promise} + */ +exports.down = function (knex, Promise) { + logger.info('[' + migrate_name + '] Migrating Down...'); + + return knex.schema.table('stream', (table) => { + table.renameColumn('forwarding_hosts', 'forwarding_host'); + }) + .then(function () { + logger.info('[' + migrate_name + '] stream Table altered'); + }); +}; diff --git a/backend/models/stream.js b/backend/models/stream.js index ed65de0fc..585c54641 100644 --- a/backend/models/stream.js +++ b/backend/models/stream.js @@ -13,6 +13,11 @@ class Stream extends Model { this.created_on = now(); this.modified_on = now(); + // Default for forwarding_hosts + if (typeof this.forwarding_hosts === 'undefined') { + this.forwarding_hosts = []; + } + // Default for meta if (typeof this.meta === 'undefined') { this.meta = {}; @@ -21,6 +26,11 @@ class Stream extends Model { $beforeUpdate () { this.modified_on = now(); + + // Sort domain_names + if (typeof this.forwarding_hosts !== 'undefined') { + this.forwarding_hosts.sort(); + } } static get name () { @@ -32,7 +42,7 @@ class Stream extends Model { } static get jsonAttributes () { - return ['meta']; + return ['forwarding_hosts', 'meta']; } static get relationMappings () { diff --git a/backend/schema/endpoints/streams.json b/backend/schema/endpoints/streams.json index 159c8036e..9afb8bf71 100644 --- a/backend/schema/endpoints/streams.json +++ b/backend/schema/endpoints/streams.json @@ -20,20 +20,26 @@ "minimum": 1, "maximum": 65535 }, - "forwarding_host": { - "anyOf": [ - { - "$ref": "../definitions.json#/definitions/domain_name" - }, - { - "type": "string", - "format": "ipv4" - }, - { - "type": "string", - "format": "ipv6" - } - ] + "forwarding_hosts": { + "type": "array", + "minItems": 1, + "maxItems": 15, + "uniqueItems": true, + "items": { + "anyOf": [ + { + "$ref": "../definitions.json#/definitions/domain_name" + }, + { + "type": "string", + "format": "ipv4" + }, + { + "type": "string", + "format": "ipv6" + } + ] + } }, "forwarding_port": { "type": "integer", @@ -66,8 +72,8 @@ "incoming_port": { "$ref": "#/definitions/incoming_port" }, - "forwarding_host": { - "$ref": "#/definitions/forwarding_host" + "forwarding_hosts": { + "$ref": "#/definitions/forwarding_hosts" }, "forwarding_port": { "$ref": "#/definitions/forwarding_port" @@ -118,15 +124,15 @@ "additionalProperties": false, "required": [ "incoming_port", - "forwarding_host", + "forwarding_hosts", "forwarding_port" ], "properties": { "incoming_port": { "$ref": "#/definitions/incoming_port" }, - "forwarding_host": { - "$ref": "#/definitions/forwarding_host" + "forwarding_hosts": { + "$ref": "#/definitions/forwarding_hosts" }, "forwarding_port": { "$ref": "#/definitions/forwarding_port" @@ -165,8 +171,8 @@ "incoming_port": { "$ref": "#/definitions/incoming_port" }, - "forwarding_host": { - "$ref": "#/definitions/forwarding_host" + "forwarding_hosts": { + "$ref": "#/definitions/forwarding_hosts" }, "forwarding_port": { "$ref": "#/definitions/forwarding_port" diff --git a/backend/templates/stream.conf b/backend/templates/stream.conf index 76159a646..c7848223c 100644 --- a/backend/templates/stream.conf +++ b/backend/templates/stream.conf @@ -3,6 +3,13 @@ # ------------------------------------------------------------ {% if enabled %} + +upstream stream_{{ incoming_port }}_tcp { + {% for forwarding_host in forwarding_hosts %} + server {{ forwarding_host }}:{{ forwarding_port }}; + {%- endfor %} +} + {% if tcp_forwarding == 1 or tcp_forwarding == true -%} server { listen {{ incoming_port }}; @@ -12,7 +19,7 @@ server { #listen [::]:{{ incoming_port }}; {% endif %} - proxy_pass {{ forwarding_host }}:{{ forwarding_port }}; + proxy_pass stream_{{ incoming_port }}_tcp; # Custom include /data/nginx/custom/server_stream[.]conf; @@ -20,18 +27,26 @@ server { } {% endif %} {% if udp_forwarding == 1 or udp_forwarding == true %} + +upstream stream_{{ incoming_port }}_udp { + {% for forwarding_host in forwarding_hosts %} + server {{ forwarding_host }}:{{ forwarding_port }}; + {%- endfor %} +} + server { listen {{ incoming_port }} udp; {% if ipv6 -%} listen [::]:{{ incoming_port }} udp; {% else -%} - #listen [::]:{{ incoming_port }} udp; + #listen [::]:{{ incoming_port }} udp; {% endif %} - proxy_pass {{ forwarding_host }}:{{ forwarding_port }}; + + proxy_pass stream_{{ incoming_port }}_udp; # Custom include /data/nginx/custom/server_stream[.]conf; include /data/nginx/custom/server_stream_udp[.]conf; } {% endif %} -{% endif %} \ No newline at end of file +{% endif %} diff --git a/frontend/js/app/nginx/stream/form.ejs b/frontend/js/app/nginx/stream/form.ejs index eb80c3737..d6bfaa8d7 100644 --- a/frontend/js/app/nginx/stream/form.ejs +++ b/frontend/js/app/nginx/stream/form.ejs @@ -14,8 +14,8 @@
- - + +
diff --git a/frontend/js/app/nginx/stream/form.js b/frontend/js/app/nginx/stream/form.js index be8fc8bc2..e4d47e789 100644 --- a/frontend/js/app/nginx/stream/form.js +++ b/frontend/js/app/nginx/stream/form.js @@ -6,6 +6,8 @@ const template = require('./form.ejs'); require('jquery-serializejson'); require('jquery-mask-plugin'); require('selectize'); +const Helpers = require("../../../lib/helpers"); +const certListItemTemplate = require("../certificates-list-item.ejs"); module.exports = Mn.View.extend({ template: template, @@ -13,7 +15,7 @@ module.exports = Mn.View.extend({ ui: { form: 'form', - forwarding_host: 'input[name="forwarding_host"]', + forwarding_hosts: 'input[name="forwarding_hosts"]', type_error: '.forward-type-error', buttons: '.modal-footer button', switches: '.custom-switch-input', @@ -48,6 +50,10 @@ module.exports = Mn.View.extend({ data.tcp_forwarding = !!data.tcp_forwarding; data.udp_forwarding = !!data.udp_forwarding; + if (typeof data.forwarding_hosts === 'string' && data.forwarding_hosts) { + data.forwarding_hosts = data.forwarding_hosts.split(','); + } + let method = App.Api.Nginx.Streams.create; let is_new = true; @@ -76,6 +82,24 @@ module.exports = Mn.View.extend({ } }, + onRender: function () { + let view = this; + + // Domain names + this.ui.forwarding_hosts.selectize({ + delimiter: ',', + persist: false, + maxOptions: 15, + create: function (input) { + return { + value: input, + text: input + }; + }, + createFilter: /^(?:\*\.)?(?:[^.*]+\.?)+[^.]$/ + }); + }, + initialize: function (options) { if (typeof options.model === 'undefined' || !options.model) { this.model = new StreamModel.Model(); diff --git a/frontend/js/app/nginx/stream/list/item.ejs b/frontend/js/app/nginx/stream/list/item.ejs index a8ff83d4c..8c2a21aa6 100644 --- a/frontend/js/app/nginx/stream/list/item.ejs +++ b/frontend/js/app/nginx/stream/list/item.ejs @@ -12,7 +12,11 @@
-
<%- forwarding_host %>:<%- forwarding_port %>
+
+ <% forwarding_hosts.map(function(forwarding_host) { %> + <%- forwarding_host %> + <% }); %> +
@@ -24,6 +28,11 @@ <% } %>
+ +
+ <%- forwarding_port %> +
+ <% var o = isOnline(); @@ -50,4 +59,4 @@ -<% } %> \ No newline at end of file +<% } %> diff --git a/frontend/js/app/nginx/stream/list/main.ejs b/frontend/js/app/nginx/stream/list/main.ejs index 5304f6145..d778f5caa 100644 --- a/frontend/js/app/nginx/stream/list/main.ejs +++ b/frontend/js/app/nginx/stream/list/main.ejs @@ -3,6 +3,7 @@ <%- i18n('streams', 'incoming-port') %> <%- i18n('str', 'destination') %> <%- i18n('streams', 'protocol') %> + <%- i18n('streams', 'forwarding-port') %> <%- i18n('str', 'status') %> <% if (canManage) { %>   diff --git a/frontend/js/models/stream.js b/frontend/js/models/stream.js index ba035429a..9c6159690 100644 --- a/frontend/js/models/stream.js +++ b/frontend/js/models/stream.js @@ -9,7 +9,7 @@ const model = Backbone.Model.extend({ created_on: null, modified_on: null, incoming_port: null, - forwarding_host: null, + forwarding_hosts: [], forwarding_port: null, tcp_forwarding: true, udp_forwarding: false,