From 9ecce40141344274b2364efd2ed117181aeb40e0 Mon Sep 17 00:00:00 2001 From: Ryan Melton Date: Sat, 25 Feb 2023 14:23:03 -0700 Subject: [PATCH 1/3] Switch Traefik to HTTP endpoint for dynamic config --- .../controllers/microservices_controller.rb | 38 ++++++++++++++- .../app/controllers/tools_controller.rb | 2 +- openc3-cosmos-cmd-tlm-api/config/routes.rb | 1 + .../packages/openc3-cosmos-demo/plugin.txt | 6 ++- openc3-traefik/traefik-allow-http.yaml | 8 ++-- openc3-traefik/traefik-dev-base.yaml | 8 ++-- openc3-traefik/traefik-dev.yaml | 8 ++-- openc3-traefik/traefik-letsencrypt.yaml | 8 ++-- openc3-traefik/traefik-ssl.yaml | 8 ++-- openc3-traefik/traefik.yaml | 8 ++-- .../lib/openc3/models/microservice_model.rb | 3 -- openc3/lib/openc3/models/plugin_model.rb | 4 +- openc3/lib/openc3/models/traefik_model.rb | 47 ------------------- 13 files changed, 64 insertions(+), 85 deletions(-) delete mode 100644 openc3/lib/openc3/models/traefik_model.rb diff --git a/openc3-cosmos-cmd-tlm-api/app/controllers/microservices_controller.rb b/openc3-cosmos-cmd-tlm-api/app/controllers/microservices_controller.rb index 35f7d5e5eb..63b5a83ca4 100644 --- a/openc3-cosmos-cmd-tlm-api/app/controllers/microservices_controller.rb +++ b/openc3-cosmos-cmd-tlm-api/app/controllers/microservices_controller.rb @@ -17,7 +17,7 @@ # All changes Copyright 2022, OpenC3, Inc. # All Rights Reserved # -# This file may also be used under the terms of a commercial license +# This file may also be used under the terms of a commercial license # if purchased from OpenC3, Inc. require 'openc3/models/microservice_model' @@ -26,4 +26,40 @@ class MicroservicesController < ModelController def initialize @model_class = OpenC3::MicroserviceModel end + + def traefik + result = {} + result['http'] = {} + http = result['http'] + http['routers'] = {} + routers = http['routers'] + http['services'] = {} + services = http['services'] + models = OpenC3::MicroserviceModel.all + models.each do |microservice_name, microservice| + prefix = microservice['prefix'] + ports = microservice['ports'] + if prefix and ports[0][0] + port = ports[0][0].to_i + prefix = '/' + prefix unless prefix[0] == '/' + if ENV['KUBERNETES_SERVICE_HOST'] + url = "http://#{microservice_name.downcase.gsub('__', '-').gsub('_', '-')}-service:#{port}" + else + url = "http://openc3-operator:#{port}" + end + service_name = microservice_name + router_name = microservice_name + services[service_name] = {} + services[service_name]['loadBalancer'] = {} + services[service_name]['loadBalancer']['passHostHeader'] = false + services[service_name]['loadBalancer']['servers'] = [] + services[service_name]['loadBalancer']['servers'] << {"url" => url} + routers[router_name] = {} + routers[router_name]['rule'] = "PathPrefix(`#{prefix}`)" + routers[router_name]['service'] = service_name + routers[router_name]['priority'] = 20 + end + end + render :json => result + end end diff --git a/openc3-cosmos-cmd-tlm-api/app/controllers/tools_controller.rb b/openc3-cosmos-cmd-tlm-api/app/controllers/tools_controller.rb index 046295c9db..cc6be5cbf7 100644 --- a/openc3-cosmos-cmd-tlm-api/app/controllers/tools_controller.rb +++ b/openc3-cosmos-cmd-tlm-api/app/controllers/tools_controller.rb @@ -17,7 +17,7 @@ # All changes Copyright 2022, OpenC3, Inc. # All Rights Reserved # -# This file may also be used under the terms of a commercial license +# This file may also be used under the terms of a commercial license # if purchased from OpenC3, Inc. require 'openc3/models/tool_model' diff --git a/openc3-cosmos-cmd-tlm-api/config/routes.rb b/openc3-cosmos-cmd-tlm-api/config/routes.rb index c6103ecdf4..d7350699d1 100644 --- a/openc3-cosmos-cmd-tlm-api/config/routes.rb +++ b/openc3-cosmos-cmd-tlm-api/config/routes.rb @@ -192,6 +192,7 @@ get "/time" => "time#get_current" get "map.json" => "tools#importmap" + get "/traefik" => "microservices#traefik" post "/redis/exec" => "redis#execute_raw" end diff --git a/openc3-cosmos-init/plugins/packages/openc3-cosmos-demo/plugin.txt b/openc3-cosmos-init/plugins/packages/openc3-cosmos-demo/plugin.txt index 07534ac92c..10bb2ecb37 100644 --- a/openc3-cosmos-init/plugins/packages/openc3-cosmos-demo/plugin.txt +++ b/openc3-cosmos-init/plugins/packages/openc3-cosmos-demo/plugin.txt @@ -15,9 +15,7 @@ VARIABLE templated_int_name TEMPLATED_INT VARIABLE demo_tool_name Demo VARIABLE example_microservice_name openc3-example VARIABLE templated_microservice_name openc3-templated -VARIABLE example_host openc3-operator VARIABLE example_port 9999 -VARIABLE templated_host openc3-operator VARIABLE templated_port 5025 VARIABLE inst_router_port 7779 VARIABLE log_retain_time 172800 @@ -78,6 +76,8 @@ VARIABLE reduced_log_retain_time 2592000 <% end %> <% if include_example and include_example_int %> + # This expression builds the correct hostname for Open Source or Enterprise Edition in Kubernetes + <% example_host = ENV['KUBERNETES_SERVICE_HOST'] ? "#{scope}-user-#{example_microservice_name.downcase.gsub('__', '-').gsub('_', '-')}-service" : "openc3-operator" %> INTERFACE <%= example_int_name %> example_interface.rb <%= example_host %> <%= example_port %> PROTOCOL WRITE MyRejectProtocol MAP_TARGET <%= example_target_name %> @@ -86,6 +86,8 @@ VARIABLE reduced_log_retain_time 2592000 <% end %> <% if include_templated and include_templated_int %> + # This expression builds the correct hostname for Open Source or Enterprise Edition in Kubernetes + <% templated_host = ENV['KUBERNETES_SERVICE_HOST'] ? "#{scope}-user-#{templated_microservice_name.downcase.gsub('__', '-').gsub('_', '-')}-service" : "openc3-operator" %> INTERFACE <%= templated_int_name %> templated_interface.rb <%= templated_host %> <%= templated_port %> <%= templated_port %> 5.0 nil TEMPLATE 0xA 0xA MAP_TARGET <%= templated_target_name %> DONT_CONNECT diff --git a/openc3-traefik/traefik-allow-http.yaml b/openc3-traefik/traefik-allow-http.yaml index 1beb316e73..ec302c03b6 100644 --- a/openc3-traefik/traefik-allow-http.yaml +++ b/openc3-traefik/traefik-allow-http.yaml @@ -104,11 +104,9 @@ http: providers: file: filename: /etc/traefik/traefik.yaml - redis: - endpoints: - - "openc3-redis:6379" - username: "traefik" - password: "traefikpassword" + http: + endpoint: "http://openc3-cosmos-cmd-tlm-api:2901/openc3-api/traefik" + pollInterval: "5s" accessLog: {} # api: # dashboard: true diff --git a/openc3-traefik/traefik-dev-base.yaml b/openc3-traefik/traefik-dev-base.yaml index 37b08da21b..05a9468cc0 100644 --- a/openc3-traefik/traefik-dev-base.yaml +++ b/openc3-traefik/traefik-dev-base.yaml @@ -80,11 +80,9 @@ http: providers: file: filename: /etc/traefik/traefik.yaml - redis: - endpoints: - - "openc3-redis:6379" - username: "traefik" - password: "traefikpassword" + http: + endpoint: "http://openc3-cosmos-cmd-tlm-api:2901/openc3-api/traefik" + pollInterval: "5s" accessLog: {} # api: # dashboard: true diff --git a/openc3-traefik/traefik-dev.yaml b/openc3-traefik/traefik-dev.yaml index d5f294f9f4..c7417d4e42 100644 --- a/openc3-traefik/traefik-dev.yaml +++ b/openc3-traefik/traefik-dev.yaml @@ -100,11 +100,9 @@ http: providers: file: filename: /etc/traefik/traefik.yaml - redis: - endpoints: - - "openc3-redis:6379" - username: "traefik" - password: "traefikpassword" + http: + endpoint: "http://openc3-cosmos-cmd-tlm-api:2901/openc3-api/traefik" + pollInterval: "5s" accessLog: {} # api: # dashboard: true diff --git a/openc3-traefik/traefik-letsencrypt.yaml b/openc3-traefik/traefik-letsencrypt.yaml index 5746f03142..96b77fd7a4 100644 --- a/openc3-traefik/traefik-letsencrypt.yaml +++ b/openc3-traefik/traefik-letsencrypt.yaml @@ -149,11 +149,9 @@ http: providers: file: filename: /etc/traefik/traefik.yaml - redis: - endpoints: - - "openc3-redis:6379" - username: "traefik" - password: "traefikpassword" + http: + endpoint: "http://openc3-cosmos-cmd-tlm-api:2901/openc3-api/traefik" + pollInterval: "5s" accessLog: {} # api: # dashboard: true diff --git a/openc3-traefik/traefik-ssl.yaml b/openc3-traefik/traefik-ssl.yaml index 5c95f3d0d5..1b1bc7e5a7 100644 --- a/openc3-traefik/traefik-ssl.yaml +++ b/openc3-traefik/traefik-ssl.yaml @@ -128,11 +128,9 @@ http: providers: file: filename: /etc/traefik/traefik.yaml - redis: - endpoints: - - "openc3-redis:6379" - username: "traefik" - password: "traefikpassword" + http: + endpoint: "http://openc3-cosmos-cmd-tlm-api:2901/openc3-api/traefik" + pollInterval: "5s" accessLog: {} # api: # dashboard: true diff --git a/openc3-traefik/traefik.yaml b/openc3-traefik/traefik.yaml index 372fe48434..df4c27870e 100644 --- a/openc3-traefik/traefik.yaml +++ b/openc3-traefik/traefik.yaml @@ -104,11 +104,9 @@ http: providers: file: filename: /etc/traefik/traefik.yaml - redis: - endpoints: - - "openc3-redis:6379" - username: "traefik" - password: "traefikpassword" + http: + endpoint: "http://openc3-cosmos-cmd-tlm-api:2901/openc3-api/traefik" + pollInterval: "5s" accessLog: {} # api: diff --git a/openc3/lib/openc3/models/microservice_model.rb b/openc3/lib/openc3/models/microservice_model.rb index 2bb77bfd21..4fd57c99f0 100644 --- a/openc3/lib/openc3/models/microservice_model.rb +++ b/openc3/lib/openc3/models/microservice_model.rb @@ -23,7 +23,6 @@ require 'openc3/top_level' require 'openc3/models/model' require 'openc3/models/metric_model' -require 'openc3/models/traefik_model' require 'openc3/utilities/bucket' module OpenC3 @@ -225,7 +224,6 @@ def deploy(gem_path, variables, validate_only: false) end end unless validate_only - TraefikModel.register_route(microservice_name: @name, port: @ports[0][0], prefix: @prefix) if @ports[0] and ports[0][0] and @prefix ConfigTopic.write({ kind: 'created', type: 'microservice', name: @name, plugin: @plugin }, scope: @scope) end end @@ -235,7 +233,6 @@ def undeploy @bucket.list_objects(bucket: ENV['OPENC3_CONFIG_BUCKET'], prefix: prefix).each do |object| @bucket.delete_object(bucket: ENV['OPENC3_CONFIG_BUCKET'], key: object.key) end - TraefikModel.unregister_route(microservice_name: @name, port: @ports[0][0], prefix: @prefix) if @ports[0] and ports[0][0] and @prefix ConfigTopic.write({ kind: 'deleted', type: 'microservice', name: @name, plugin: @plugin }, scope: @scope) rescue Exception => error Logger.error("Error undeploying microservice model #{@name} in scope #{@scope} due to #{error}") diff --git a/openc3/lib/openc3/models/plugin_model.rb b/openc3/lib/openc3/models/plugin_model.rb index ffd82ecc71..9c50a3f5a6 100644 --- a/openc3/lib/openc3/models/plugin_model.rb +++ b/openc3/lib/openc3/models/plugin_model.rb @@ -44,7 +44,7 @@ module OpenC3 class PluginModel < Model PRIMARY_KEY = 'openc3_plugins' # Reserved VARIABLE names. See local_mode.rb: update_local_plugin() - RESERVED_VARIABLE_NAMES = ['target_name', 'microservice_name'] + RESERVED_VARIABLE_NAMES = ['target_name', 'microservice_name', 'scope'] attr_accessor :variables attr_accessor :plugin_txt_lines @@ -198,6 +198,8 @@ def self.install_phase2(plugin_hash, scope:, gem_file_path: nil, validate_only: tf.close plugin_txt_path = tf.path variables = plugin_hash['variables'] + variables ||= {} + variables['scope'] = scope if File.exist?(plugin_txt_path) parser = OpenC3::ConfigParser.new("https://openc3.com") diff --git a/openc3/lib/openc3/models/traefik_model.rb b/openc3/lib/openc3/models/traefik_model.rb deleted file mode 100644 index d374743f1f..0000000000 --- a/openc3/lib/openc3/models/traefik_model.rb +++ /dev/null @@ -1,47 +0,0 @@ -# encoding: ascii-8bit - -# Copyright 2023 OpenC3, Inc. -# All Rights Reserved. -# -# This program is free software; you can modify and/or redistribute it -# under the terms of the GNU Affero General Public License -# as published by the Free Software Foundation; version 3 with -# attribution addendums as found in the LICENSE.txt -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -# GNU Affero General Public License for more details. -# -# This file may also be used under the terms of a commercial license -# if purchased from OpenC3, Inc. - -require 'openc3/utilities/store' - -module OpenC3 - class TraefikModel - def self.register_route(microservice_name:, port:, prefix:, priority: 20) - prefix = '/' + prefix unless prefix[0] == '/' - if ENV['KUBERNETES_SERVICE_HOST'] - url = "http://#{microservice_name.gsub('__', '-')}:#{port}" - else - url = "http://openc3-operator:#{port}" - end - service_name = microservice_name - router_name = microservice_name - Store.set("traefik/http/services/#{service_name}/loadbalancer/servers/0/url", url) - Store.set("traefik/http/routers/#{router_name}/service", service_name) - Store.set("traefik/http/routers/#{router_name}/priority", priority) - Store.set("traefik/http/routers/#{router_name}/rule", "PathPrefix(`#{prefix}`)") - end - - def self.unregister_route(microservice_name:) - service_name = microservice_name - router_name = microservice_name - Store.del("traefik/http/routers/#{router_name}/rule") - Store.del("traefik/http/routers/#{router_name}/priority") - Store.del("traefik/http/routers/#{router_name}/service") - Store.del("traefik/http/services/#{service_name}/loadbalancer/servers/0/url") - end - end -end From fd709c206ccd1e98a0d0f9163dbe772f63d9d243 Mon Sep 17 00:00:00 2001 From: Ryan Melton Date: Sat, 25 Feb 2023 14:26:47 -0700 Subject: [PATCH 2/3] remove traefik redis account --- openc3-redis/users.acl | 1 - 1 file changed, 1 deletion(-) diff --git a/openc3-redis/users.acl b/openc3-redis/users.acl index 35ab851719..b525897ef2 100644 --- a/openc3-redis/users.acl +++ b/openc3-redis/users.acl @@ -2,5 +2,4 @@ user healthcheck on nopass -@all +cluster|info +ping user openc3 on >openc3password allkeys allchannels -@all +@read +@write +@pubsub +@connection +@transaction +info user scriptrunner on >scriptrunnerpassword resetkeys resetchannels ~running-script* ~*script-locks ~*script-breakpoints ~*openc3_log_messages &_action_cable_internal &script-api:* -@all +@read +@write +@pubsub +@hash +@connection user admin on >adminpassword +@admin -user traefik on >traefikpassword allchannels ~traefik* -@all +@admin +@read +@pubsub user default off From b6f6fd145fd2786f4e9353037fccb1f0fccf8ca4 Mon Sep 17 00:00:00 2001 From: Ryan Melton Date: Sat, 25 Feb 2023 17:14:10 -0700 Subject: [PATCH 3/3] fix specs --- openc3/spec/models/plugin_model_spec.rb | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/openc3/spec/models/plugin_model_spec.rb b/openc3/spec/models/plugin_model_spec.rb index 725878263b..f1b61f1f42 100644 --- a/openc3/spec/models/plugin_model_spec.rb +++ b/openc3/spec/models/plugin_model_spec.rb @@ -226,8 +226,8 @@ module OpenC3 # Just stub the instance deploy method expect(GemModel).to receive(:install).and_return(nil) - expect_any_instance_of(ToolModel).to receive(:deploy).with(anything, {}, validate_only: false).and_return(nil) - expect_any_instance_of(TargetModel).to receive(:deploy).with(anything, {}, validate_only: false).and_return(nil) + expect_any_instance_of(ToolModel).to receive(:deploy).with(anything, {"scope" => 'DEFAULT'}, validate_only: false).and_return(nil) + expect_any_instance_of(TargetModel).to receive(:deploy).with(anything, {"scope" => 'DEFAULT'}, validate_only: false).and_return(nil) plugin_model = PluginModel.install_phase2({"name" => "name", "variables" => {}, "plugin_txt_lines" => plugin_txt_lines}, scope: "DEFAULT") expect(plugin_model['needs_dependencies']).to eql true end @@ -255,8 +255,8 @@ module OpenC3 # Just stub the instance deploy method expect(GemModel).to receive(:install).and_return(nil) - expect_any_instance_of(ToolModel).to receive(:deploy).with(anything, {}, validate_only: false).and_return(nil) - expect_any_instance_of(TargetModel).to receive(:deploy).with(anything, {}, validate_only: false).and_return(nil) + expect_any_instance_of(ToolModel).to receive(:deploy).with(anything, {"scope" => 'DEFAULT'}, validate_only: false).and_return(nil) + expect_any_instance_of(TargetModel).to receive(:deploy).with(anything, {"scope" => 'DEFAULT'}, validate_only: false).and_return(nil) plugin_model = PluginModel.install_phase2({"name" => "name", "variables" => {}, "plugin_txt_lines" => plugin_txt_lines}, scope: "DEFAULT") expect(plugin_model['needs_dependencies']).to eql true end @@ -285,8 +285,8 @@ module OpenC3 # Just stub the instance deploy method expect(GemModel).to receive(:install).and_return(nil) - expect_any_instance_of(ToolModel).to receive(:deploy).with(anything, {}, validate_only: false).and_return(nil) - expect_any_instance_of(TargetModel).to receive(:deploy).with(anything, {}, validate_only: false).and_return(nil) + expect_any_instance_of(ToolModel).to receive(:deploy).with(anything, {"scope" => 'DEFAULT'}, validate_only: false).and_return(nil) + expect_any_instance_of(TargetModel).to receive(:deploy).with(anything, {"scope" =>'DEFAULT'}, validate_only: false).and_return(nil) plugin_model = PluginModel.install_phase2({"name" => "name", "variables" => {}, "plugin_txt_lines" => plugin_txt_lines}, scope: "DEFAULT") expect(plugin_model['needs_dependencies']).to eql true end