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

Add new class ServiceContainerTemplate. #15429

Merged
merged 3 commits into from
Jul 13, 2017
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: 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,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I saw the other PR you created OrchestrationStack for OpenShift and its Status class. It then makes sense to revert your earlier code to use the Status subclass, but here you need to create the stack instance with the right class.

: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
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need to update_attributes for the case in line 23.

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