-
Notifications
You must be signed in to change notification settings - Fork 898
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #15429 from lfu/service_container_template
Add new class ServiceContainerTemplate.
- Loading branch information
Showing
5 changed files
with
335 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
65 changes: 65 additions & 0 deletions
65
app/models/manageiq/providers/container_manager/orchestration_stack.rb
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |