Skip to content

Commit

Permalink
Merge pull request #15429 from lfu/service_container_template
Browse files Browse the repository at this point in the history
Add new class ServiceContainerTemplate.
  • Loading branch information
gmcculloug authored Jul 13, 2017
2 parents 8ad46ce + b86af5a commit a50d793
Show file tree
Hide file tree
Showing 5 changed files with 335 additions and 0 deletions.
2 changes: 2 additions & 0 deletions app/models/manageiq/providers/container_manager.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
module ManageIQ::Providers
class ContainerManager < BaseManager
require_nested :OrchestrationStack

include AvailabilityMixin
include HasMonitoringManagerMixin
include SupportsFeatureMixin
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
class ManageIQ::Providers::ContainerManager::OrchestrationStack < ::OrchestrationStack
belongs_to :ext_management_system, :foreign_key => :ems_id, :class_name => "ManageIQ::Providers::ContainerManager"
belongs_to :container_template, :foreign_key => :orchestration_template_id, :class_name => "ContainerTemplate"

def self.create_stack(container_template, params, project_name)
new(:name => container_template.name,
:ext_management_system => container_template.ext_management_system,
:container_template => container_template).tap do |stack|
stack.send(:add_provider_objects, raw_create_stack(container_template, params, project_name))
stack.save!
end
end

def self.raw_create_stack(container_template, params, project_name)
container_template.instantiate(params, project_name)
rescue => err
_log.error("Failed to provision from container template [#{container_template.name}], error: [#{err}]")
raise MiqException::MiqOrchestrationProvisionError, err.to_s, err.backtrace
end

def self.status_class
"#{name}::Status".constantize
end

def raw_status
failed = resources.any? { |obj| obj.resource_status == 'failed' }
if failed
update_attributes(:status => 'failed')
return self.class.status_class.new('failed', nil)
end

done = resources.all? do |obj|
miq_class = obj.resource_category
miq_obj = miq_class.constantize.find_by(:ems_ref => obj.ems_ref) if miq_class
obj.update_attributes(:resource_status => 'succeeded') if miq_obj
miq_class.nil? || miq_obj
end

update_attributes(:status => 'succeeded') if done
message = done ? "completed" : "in progress"

self.class.status_class.new(message, nil)
end

def add_provider_objects(objects)
self.resources = objects.collect { |object| add_provider_object(object) }
end
private :add_provider_objects

def add_provider_object(object)
options = {
:name => object[:metadata][:name],
:physical_resource => object[:metadata][:namespace],
:ems_ref => object[:metadata][:uid],
:start_time => object[:metadata][:creationTimestamp],
:logical_resource => object[:kind],
:resource_category => object[:miq_class],
:description => object[:apiVersion],
:resource_status => 'creating'
}
options[:resource_status] = 'failed' if object[:kind].blank?
OrchestrationStackResource.new(options)
end
private :add_provider_object
end
113 changes: 113 additions & 0 deletions app/models/service_container_template.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
class ServiceContainerTemplate < ServiceGeneric
delegate :container_template, :container_manager, :to => :service_template, :allow_nil => true

# A chance for taking options from automate script to override options from a service dialog
def preprocess(action, new_options = {})
return unless action == ResourceAction::PROVISION

unless new_options.blank?
_log.info("Override with new options:")
$log.log_hashes(new_options)
end

save_action_options(action, new_options)
end

def execute(action)
return unless action == ResourceAction::PROVISION

opts = get_action_options(action)

_log.info("Container template provisioning with options:")
$log.log_hashes(opts)

params = process_parameters(opts[:parameters])
stack_klass = "#{container_manager.class.name}::OrchestrationStack".constantize
new_stack = stack_klass.create_stack(container_template, params, opts[:container_project_name])
_log.info("Container provisioning with template ID: [#{id}] name:[#{name}] was initiated.")

add_resource!(new_stack, :name => action)
end

def check_completed(action)
return [true, 'not supported'] unless action == ResourceAction::PROVISION

status, reason = stack.raw_status.normalized_status
done = status != 'transient'
message = status == 'create_complete' ? nil : reason
[done, message]
end

def refresh(action)
end

def check_refreshed(_action)
[true, nil]
end

def on_error(action)
_log.info("on_error called for service: [#{name}] action: [#{action}]")
end

def stack
service_resources.find_by(:name => ResourceAction::PROVISION, :resource_type => 'OrchestrationStack').try(:resource)
end

private

def process_parameters(inputs)
params = container_template.container_template_parameters.to_a
inputs.each do |key, value|
match = params.find { |p| p.name == key.to_s }
match.value = value if match
end
params
end

def get_action_options(action)
options[action_option_key(action)].deep_dup
end

def save_action_options(action, overrides)
return unless action == ResourceAction::PROVISION

action_options = {
:container_project_name => project_name(overrides),
:parameters => parameters_from_dialog.with_indifferent_access.merge(overrides)
}

options[action_option_key(action)] = action_options
save!
end

def action_option_key(action)
"#{action.downcase}_options".to_sym
end

def parameters_from_dialog
params =
options[:dialog].each_with_object({}) do |(attr, val), obj|
var_key = attr.sub(/dialog_param_/, '')
obj[var_key] = val unless var_key == attr
end

params.blank? ? {} : params
end

def project_name(overrides)
# :dialog option should specify the project name, either an existing project or a new project name
dialog_options = options[:dialog]
existing_name = overrides.delete(:existing_project_name) || dialog_options['dialog_existing_project_name']
new_project_name = overrides.delete(:new_project_name) || dialog_options['dialog_new_project_name']

create_project(new_project_name) if new_project_name
project_name = new_project_name || existing_name

raise _("A project is required for the container template provisioning") unless project_name
project_name
end

def create_project(name)
container_manager.create_project(:metadata => {:name => name})
end
end
3 changes: 3 additions & 0 deletions spec/factories/service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,7 @@

factory :service_ansible_playbook, :class => :ServiceAnsiblePlaybook, :parent => :service do
end

factory :service_container_template, :class => :ServiceContainerTemplate, :parent => :service do
end
end
152 changes: 152 additions & 0 deletions spec/models/service_container_template_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
describe(ServiceContainerTemplate) do
let(:action) { ResourceAction::PROVISION }
let(:stack_status) { double("ManageIQ::Providers::Openshift::ContainerManager::OrchestrationStack::Status") }
let(:stack) do
double("ManageIQ::Providers::Openshift::ContainerManager::OrchestrationStack", :resources => [created_object], :raw_status => stack_status)
end

let(:ems) do
FactoryGirl.create(:ems_openshift).tap do |ems|
allow(ems).to receive(:create_project)
end
end

let(:service) do
FactoryGirl.create(:service_container_template, :options => config_info_options.merge(dialog_options)).tap do |svc|
allow(svc).to receive(:container_manager).and_return(ems)
end
end

let(:service_with_new_project) do
FactoryGirl.create(:service_container_template, :options => config_info_options.merge(dialog_options_with_new_project)).tap do |svc|
allow(svc).to receive(:container_manager).and_return(ems)
end
end

let(:loaded_service) do
service_template = FactoryGirl.create(:service_template_container_template).tap do |st|
allow(st).to receive(:container_manager).and_return(ems)
end

FactoryGirl.create(:service_container_template,
:options => provision_options.merge(config_info_options),
:service_template => service_template).tap do |svc|
allow(svc).to receive(:container_template).and_return(container_template)
allow(svc).to receive(:stack).and_return(stack)
end
end

let(:dialog_options) do
{
:dialog => {
'dialog_existing_project_name' => 'old_project',
'dialog_param_var1' => 'value1',
'dialog_param_var2' => 'value2'
}
}
end

let(:dialog_options_with_new_project) do
{
:dialog => {
'dialog_existing_project_name' => 'old_project',
'dialog_new_project_name' => 'new_project',
'dialog_param_var1' => 'value1',
'dialog_param_var2' => 'value2'
}
}
end

let(:config_info_options) do
{
:config_info => {
:provision => {
:dialog_id => 2,
:container_template => container_template
}
}
}
end

let(:override_options) { {:new_project_name => 'override_project', :var1 => 'new_val1'} }

let(:provision_options) do
{
:provision_options => {
:container_project_name => 'my-project',
:parameters => {'var1' => 'value1', 'var2' => 'value2'}
}
}
end

let(:ctp1) { FactoryGirl.create(:container_template_parameter, :name => 'var1', :value => 'p1', :required => true) }
let(:ctp2) { FactoryGirl.create(:container_template_parameter, :name => 'var2', :value => 'p2', :required => true) }
let(:ctp3) { FactoryGirl.create(:container_template_parameter, :name => 'var3', :value => 'p3', :required => false) }
let(:container_template) do
FactoryGirl.create(:container_template, :ems_id => ems.id).tap do |ct|
ct.container_template_parameters = [ctp1, ctp2, ctp3]
end
end

let(:created_object) { FactoryGirl.create(:orchestration_stack_resource, :name => 'my-example', :resource_category => 'ContainerRoute') }
let(:object_hash) { {:apiVersion => "v1", :kind => "Route", :metadata => {:name => "dotnet-example"}} }

describe '#preprocess' do
it 'prepares job options from dialog' do
expect(ems).not_to receive(:create_project)
service.preprocess(action)
expect(service.options[:provision_options]).to have_attributes(
:container_project_name => 'old_project',
:parameters => {"var1" => "value1", "var2" => "value2"}
)
end

it 'honors new project name more than existing project name' do
expect(ems).to receive(:create_project)
service_with_new_project.preprocess(action)
expect(service_with_new_project.options[:provision_options]).to have_attributes(
:container_project_name => 'new_project',
:parameters => {"var1" => "value1", "var2" => "value2"}
)
end

it 'prepares job options combined from dialog and overrides' do
expect(ems).to receive(:create_project)
service_with_new_project.preprocess(action, override_options)
expect(service_with_new_project.options[:provision_options]).to have_attributes(
:container_project_name => 'override_project',
:parameters => {'var1' => 'new_val1', 'var2' => 'value2'}
)
end
end

describe '#execute' do
it 'Provisions with a container template' do
expect(container_template).to receive(:instantiate) do |params, project_name|
expect(project_name).to eq(provision_options.fetch_path(:provision_options, :container_project_name))
expect(params).to match_array([ctp1, ctp2, ctp3])
expect(ctp1.value).to eq(provision_options.fetch_path(:provision_options, :parameters, ctp1.name))
expect(ctp2.value).to eq(provision_options.fetch_path(:provision_options, :parameters, ctp2.name))
expect(ctp3.value).to eq(ctp3.value)
[object_hash]
end
loaded_service.execute(action)
end
end

describe '#check_completed' do
it 'created container object ends in VMDB' do
allow(stack_status).to receive(:normalized_status).and_return(%w(create_complete completed))
expect(loaded_service.check_completed(action)).to eq([true, nil])
end

it 'created container object not ends in VMDB yet' do
allow(stack_status).to receive(:normalized_status).and_return(['transient', 'in progress'])
expect(loaded_service.check_completed(action)).to eq([false, 'in progress'])
end
end

describe '#check_refreshed' do
it { expect(loaded_service.check_refreshed(action)).to eq([true, nil]) }
end
end

0 comments on commit a50d793

Please sign in to comment.