diff --git a/Gemfile b/Gemfile index b00a3ed6f08..7cde1e5c9ad 100644 --- a/Gemfile +++ b/Gemfile @@ -36,6 +36,7 @@ gem "gettext_i18n_rails", "~>1.7.2" gem "gettext_i18n_rails_js", "~>1.1.0" gem "hamlit", "~>2.7.0" gem "inifile", "~>3.0", :require => false +gem "kubeclient", "~>2.4.0", :require => false # For scaling pods at runtime gem "manageiq-api-client", "~>0.1.0", :require => false gem "manageiq-network_discovery", "~>0.1.1", :require => false gem "mime-types", "~>2.6.1", :path => "mime-types-redirector" diff --git a/lib/container_orchestrator.rb b/lib/container_orchestrator.rb new file mode 100644 index 00000000000..076e90b8851 --- /dev/null +++ b/lib/container_orchestrator.rb @@ -0,0 +1,28 @@ +class ContainerOrchestrator + TOKEN_FILE = "/run/secrets/kubernetes.io/serviceaccount/token".freeze + + def scale(deployment_config_name, replicas) + connection.patch_deployment_config(deployment_config_name, { :spec => { :replicas => replicas } }, ENV["MY_POD_NAMESPACE"]) + end + + private + + def connection + require 'kubeclient' + + @connection ||= + Kubeclient::Client.new( + manager_uri, + :auth_options => { :bearer_token_file => TOKEN_FILE }, + :ssl_options => { :verify_ssl => OpenSSL::SSL::VERIFY_NONE } + ) + end + + def manager_uri + URI::HTTPS.build( + :host => ENV["KUBERNETES_SERVICE_HOST"], + :port => ENV["KUBERNETES_SERVICE_PORT"], + :path => "/oapi" + ) + end +end diff --git a/lib/embedded_ansible.rb b/lib/embedded_ansible.rb index cee44c4c5ea..942b874607f 100644 --- a/lib/embedded_ansible.rb +++ b/lib/embedded_ansible.rb @@ -12,6 +12,7 @@ class EmbeddedAnsible HTTPS_PORT = 54_322 WAIT_FOR_ANSIBLE_SLEEP = 1.second TOWER_VERSION_FILE = "/var/lib/awx/.tower_version".freeze + ANSIBLE_DC_NAME = "manageiq-ansible".freeze def self.available? return true if MiqEnvironment::Command.is_container? @@ -52,21 +53,15 @@ def self.alive? end def self.start - if MiqEnvironment::Command.is_container? - container_start - else - appliance_start - end + MiqEnvironment::Command.is_container? ? container_start : appliance_start end def self.stop - return if MiqEnvironment::Command.is_container? - services.each { |service| LinuxAdmin::Service.new(service).stop } + MiqEnvironment::Command.is_container? ? container_stop : appliance_stop end def self.disable - return if MiqEnvironment::Command.is_container? - services.each { |service| LinuxAdmin::Service.new(service).stop.disable } + MiqEnvironment::Command.is_container? ? container_stop : appliance_disable end def self.services @@ -110,8 +105,19 @@ def self.appliance_start end private_class_method :appliance_start + def self.appliance_stop + services.each { |service| LinuxAdmin::Service.new(service).stop } + end + private_class_method :appliance_stop + + def self.appliance_disable + services.each { |service| LinuxAdmin::Service.new(service).stop.disable } + end + private_class_method :appliance_disable + def self.container_start miq_database.set_ansible_admin_authentication(:password => ENV["ANSIBLE_ADMIN_PASSWORD"]) + ContainerOrchestrator.new.scale(ANSIBLE_DC_NAME, 1) loop do break if alive? @@ -122,6 +128,10 @@ def self.container_start end private_class_method :container_start + def self.container_stop + ContainerOrchestrator.new.scale(ANSIBLE_DC_NAME, 0) + end + def self.run_setup_script(exclude_tags) json_extra_vars = { :minimum_var_space => 0, diff --git a/spec/lib/embedded_ansible_spec.rb b/spec/lib/embedded_ansible_spec.rb index 0a35139a6d0..eedef3ae9c6 100644 --- a/spec/lib/embedded_ansible_spec.rb +++ b/spec/lib/embedded_ansible_spec.rb @@ -46,24 +46,6 @@ end end - describe ".stop" do - it "doesn't attempt to stop services if running in a container" do - expect(MiqEnvironment::Command).to receive(:is_container?).and_return(true) - expect(LinuxAdmin::Service).not_to receive(:new) - - described_class.stop - end - end - - describe ".disable" do - it "doesn't attempt to disable services if running in a container" do - expect(MiqEnvironment::Command).to receive(:is_container?).and_return(true) - expect(LinuxAdmin::Service).not_to receive(:new) - - described_class.disable - end - end - context "with services" do let(:nginx_service) { double("nginx service") } let(:supervisord_service) { double("supervisord service") } @@ -417,19 +399,50 @@ end end - describe ".start when in a container" do - around do |example| - ENV["ANSIBLE_ADMIN_PASSWORD"] = "thepassword" - example.run - ENV.delete("ANSIBLE_ADMIN_PASSWORD") + context "when in a container" do + before do + expect(MiqEnvironment::Command).to receive(:is_container?).and_return(true) end - it "sets the admin password using the environment variable and waits for the service to respond" do - expect(MiqEnvironment::Command).to receive(:is_container?).and_return(true) - expect(described_class).to receive(:alive?).and_return(true) + describe ".stop" do + it "scales the ansible pod to 0 replicas" do + orch = double("ContainerOrchestrator") + expect(ContainerOrchestrator).to receive(:new).and_return(orch) - described_class.start - expect(miq_database.reload.ansible_admin_authentication.password).to eq("thepassword") + expect(orch).to receive(:scale).with("manageiq-ansible", 0) + + described_class.stop + end + end + + describe ".disable" do + it "scales the ansible pod to 0 replicas" do + orch = double("ContainerOrchestrator") + expect(ContainerOrchestrator).to receive(:new).and_return(orch) + + expect(orch).to receive(:scale).with("manageiq-ansible", 0) + + described_class.disable + end + end + + describe ".start" do + around do |example| + ENV["ANSIBLE_ADMIN_PASSWORD"] = "thepassword" + example.run + ENV.delete("ANSIBLE_ADMIN_PASSWORD") + end + + it "sets the admin password using the environment variable and waits for the service to respond" do + orch = double("ContainerOrchestrator") + expect(ContainerOrchestrator).to receive(:new).and_return(orch) + + expect(orch).to receive(:scale).with("manageiq-ansible", 1) + expect(described_class).to receive(:alive?).and_return(true) + + described_class.start + expect(miq_database.reload.ansible_admin_authentication.password).to eq("thepassword") + end end end