Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create embedded ansible deployment on demand #15963

Merged
merged 2 commits into from
Jan 12, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion app/models/embedded_ansible_worker/runner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ def provider_url

def provider_uri_hash
if MiqEnvironment::Command.is_container?
{:scheme => "https", :host => ENV["ANSIBLE_SERVICE_HOST"], :path => "/api/v1"}
{:scheme => "https", :host => ContainerEmbeddedAnsible::ANSIBLE_SERVICE_NAME, :path => "/api/v1"}
elsif Rails.env.development?
{:scheme => "http", :host => "localhost", :path => "/api/v1", :port => 54321}
else
Expand Down
4 changes: 4 additions & 0 deletions config/settings.yml
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@
:rabbitmq_image_tag: 3
:memcached_image_name: memcached
:memcached_image_tag: alpine
:container:
:image_name: manageiq/embedded-ansible
:image_tag: latest
:service_account: miq-privileged
:ems:
# provider specific settings are nested here, but they are in the provider repos
# e.g.:
Expand Down
106 changes: 101 additions & 5 deletions lib/embedded_ansible/container_embedded_ansible.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
class ContainerEmbeddedAnsible < EmbeddedAnsible
ANSIBLE_DC_NAME = "ansible".freeze
ANSIBLE_SERVICE_NAME = "ansible".freeze
ANSIBLE_SECRETS_NAME = "ansible-secrets".freeze

def self.available?
ContainerOrchestrator.available?
Expand All @@ -10,8 +11,9 @@ def self.priority
end

def start
miq_database.set_ansible_admin_authentication(:password => ENV["ANSIBLE_ADMIN_PASSWORD"])
ContainerOrchestrator.new.scale(ANSIBLE_DC_NAME, 1)
create_ansible_secret
create_ansible_service
create_ansible_deployment_config

loop do
break if alive?
Expand All @@ -22,7 +24,9 @@ def start
end

def stop
ContainerOrchestrator.new.scale(ANSIBLE_DC_NAME, 0)
orchestrator.delete_deployment_config(ANSIBLE_SERVICE_NAME)
orchestrator.delete_service(ANSIBLE_SERVICE_NAME)
orchestrator.delete_secret(ANSIBLE_SECRETS_NAME)
end

alias disable stop
Expand All @@ -36,6 +40,98 @@ def configured?
end

def api_connection
api_connection_raw(ENV["ANSIBLE_SERVICE_HOST"], ENV["ANSIBLE_SERVICE_PORT_HTTP"])
api_connection_raw(ANSIBLE_SERVICE_NAME, 80)
end

private

def create_ansible_secret
# NOTE: These keys have to match the ones in #container_environment
secret_data = {
"secret-key" => find_or_create_secret_key,
"admin-password" => find_or_create_admin_authentication.password,
"database-password" => find_or_create_database_authentication.password,
"rabbit-password" => find_or_create_rabbitmq_authentication.password
}

orchestrator.create_secret(ANSIBLE_SECRETS_NAME, secret_data)
end

def create_ansible_service
orchestrator.create_service(ANSIBLE_SERVICE_NAME, 443) do |service|
http_port = {
:name => "#{ANSIBLE_SERVICE_NAME}-80",
:port => 80,
:targetPort => 80
}
service[:spec][:ports] << http_port
end
end

def create_ansible_deployment_config
orchestrator.create_deployment_config(ANSIBLE_SERVICE_NAME) do |dc|
dc[:spec][:serviceName] = ANSIBLE_SERVICE_NAME
dc[:spec][:replicas] = 1

dc[:spec][:template][:spec][:serviceAccount] = settings.service_account
dc[:spec][:template][:spec][:serviceAccountName] = settings.service_account

container = dc[:spec][:template][:spec][:containers].first
container[:ports] = [{:containerPort => 443}, {:containerPort => 80}]
container[:livenessProbe] = liveness_probe
container[:readinessProbe] = readiness_probe
container[:env] = container_environment
container[:image] = image

container[:securityContext] = {:privileged => true}
end
end

def liveness_probe
{
:tcpSocket => {:port => 443},
:initialDelaySeconds => 480,
:timeoutSeconds => 3
}
end

def readiness_probe
{
:httpGet => {:path => "/", :port => 443, :scheme => "HTTPS"},
:initialDelaySeconds => 200,
:timeoutSeconds => 3
}
end

def container_environment
rabbit_auth = find_or_create_rabbitmq_authentication
database_auth = find_or_create_database_authentication

[
{:name => "RABBITMQ_USER_NAME", :value => rabbit_auth.userid},
{:name => "DATABASE_SERVICE_NAME", :value => ENV["POSTGRESQL_SERVICE_HOST"]},
{:name => "POSTGRESQL_DATABASE", :value => "awx"},
{:name => "POSTGRESQL_USER", :value => database_auth.userid},
{:name => "ANSIBLE_SECRET_KEY",
:valueFrom => {:secretKeyRef=>{:name => ANSIBLE_SECRETS_NAME, :key => "secret-key"}}},
{:name => "ADMIN_PASSWORD",
:valueFrom => {:secretKeyRef=>{:name => ANSIBLE_SECRETS_NAME, :key => "admin-password"}}},
{:name => "POSTGRESQL_PASSWORD",
:valueFrom => {:secretKeyRef=>{:name => ANSIBLE_SECRETS_NAME, :key => "database-password"}}},
{:name => "RABBITMQ_PASSWORD",
:valueFrom => {:secretKeyRef=>{:name => ANSIBLE_SECRETS_NAME, :key => "rabbit-password"}}}
]
end

def image
"#{settings.image_name}:#{settings.image_tag}"
end

def orchestrator
@orchestrator ||= ContainerOrchestrator.new
end

def settings
::Settings.embedded_ansible.container
end
end
85 changes: 59 additions & 26 deletions spec/lib/embedded_ansible/container_embedded_ansible_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@

describe ContainerEmbeddedAnsible do
let(:miq_database) { MiqDatabase.first }
let(:orchestrator) { double("ContainerOrchestrator") }

before do
allow(ContainerOrchestrator).to receive(:available?).and_return(true)
allow(MiqEnvironment::Command).to receive(:is_appliance?).and_return(false)
allow(Docker).to receive(:validate_version!).and_raise(RuntimeError)
allow(ContainerOrchestrator).to receive(:new).and_return(orchestrator)

FactoryGirl.create(:miq_region, :region => ApplicationRecord.my_region_number)
MiqDatabase.seed
Expand All @@ -27,49 +29,33 @@
end

describe "#start" do
around do |example|
ENV["ANSIBLE_ADMIN_PASSWORD"] = "thepassword"
example.run
ENV.delete("ANSIBLE_ADMIN_PASSWORD")
end
it "waits for the service to respond" do
expect(subject).to receive(:create_ansible_secret)
expect(subject).to receive(:create_ansible_service)
expect(subject).to receive(:create_ansible_deployment_config)

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("ansible", 1)
expect(subject).to receive(:alive?).and_return(true)

subject.start
expect(miq_database.reload.ansible_admin_authentication.password).to eq("thepassword")
end
end

describe "#stop" 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("ansible", 0)
it "removes all the previously created objects" do
expect(orchestrator).to receive(:delete_deployment_config).with("ansible")
expect(orchestrator).to receive(:delete_service).with("ansible")
expect(orchestrator).to receive(:delete_secret).with("ansible-secrets")

subject.stop
end
end

describe "#api_connection" do
around do |example|
ENV["ANSIBLE_SERVICE_HOST"] = "192.0.2.1"
ENV["ANSIBLE_SERVICE_PORT_HTTP"] = "1234"
example.run
ENV.delete("ANSIBLE_SERVICE_HOST")
ENV.delete("ANSIBLE_SERVICE_PORT_HTTP")
end

it "connects to the ansible service when running in a container" do
it "connects to the ansible service" do
miq_database.set_ansible_admin_authentication(:password => "adminpassword")

expect(AnsibleTowerClient::Connection).to receive(:new).with(
:base_url => "http://192.0.2.1:1234/api/v1",
:base_url => "http://ansible/api/v1",
:username => "admin",
:password => "adminpassword",
:verify_ssl => 0
Expand All @@ -78,4 +64,51 @@
subject.api_connection
end
end

describe "#create_ansible_secret (private)" do
it "uses the existing values in our database" do
miq_database.ansible_secret_key = "secretkey"
miq_database.set_ansible_rabbitmq_authentication(:password => "rabbitpass")
miq_database.set_ansible_admin_authentication(:password => "12345")
miq_database.set_ansible_database_authentication(:password => "dbpassword")

expected_data = {
"secret-key" => "secretkey",
"admin-password" => "12345",
"database-password" => "dbpassword",
"rabbit-password" => "rabbitpass"
}
expect(orchestrator).to receive(:create_secret).with("ansible-secrets", expected_data)
subject.send(:create_ansible_secret)
end
end

describe "#container_environment (private)" do
let!(:db_user) { miq_database.set_ansible_database_authentication(:password => "dbpassword").userid }
let!(:rabbit_user) { miq_database.set_ansible_rabbitmq_authentication(:password => "rabbitpass").userid }

around do |example|
ENV["POSTGRESQL_SERVICE_HOST"] = "postgres.example.com"
example.run
ENV.delete("POSTGRESQL_SERVICE_HOST")
end

it "sends RABBITMQ_USER_NAME value from the database" do
env_array = subject.send(:container_environment)
env_entry = {:name => "RABBITMQ_USER_NAME", :value => rabbit_user}
expect(env_array).to include(env_entry)
end

it "sends DATABASE_SERVICE_NAME value from the PG service host" do
env_array = subject.send(:container_environment)
env_entry = {:name => "DATABASE_SERVICE_NAME", :value => "postgres.example.com"}
expect(env_array).to include(env_entry)
end

it "sends POSTGRESQL_USER value from the database" do
env_array = subject.send(:container_environment)
env_entry = {:name => "POSTGRESQL_USER", :value => db_user}
expect(env_array).to include(env_entry)
end
end
end
8 changes: 1 addition & 7 deletions spec/models/embedded_ansible_worker/runner_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -106,12 +106,6 @@
expect(MiqEnvironment::Command).to receive(:is_container?).and_return(true)
end

around do |example|
ENV["ANSIBLE_SERVICE_HOST"] = "192.0.2.1"
example.run
ENV.delete("ANSIBLE_SERVICE_HOST")
end

it "creates the provider with the service name for the URL" do
expect(worker).to receive(:remove_demo_data).with(api_connection)
expect(worker).to receive(:ensure_initial_objects)
Expand All @@ -121,7 +115,7 @@

provider = ManageIQ::Providers::EmbeddedAnsible::Provider.first
expect(provider.zone).to eq(miq_server.zone)
expect(provider.default_endpoint.url).to eq("https://192.0.2.1/api/v1")
expect(provider.default_endpoint.url).to eq("https://ansible/api/v1")
userid, password = provider.auth_user_pwd
expect(userid).to eq("admin")
expect(password).to eq("secret")
Expand Down