From ec70f64c657ce65866c12c1cc53ee04f6cbd9ce7 Mon Sep 17 00:00:00 2001 From: Jillian Tullo Date: Fri, 11 Nov 2016 14:09:50 -0500 Subject: [PATCH 1/9] add create service template to REST api --- config/api.yml | 4 ++ spec/requests/api/service_templates_spec.rb | 51 +++++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/config/api.yml b/config/api.yml index 00f0fb44143..723f222c0a5 100644 --- a/config/api.yml +++ b/config/api.yml @@ -1691,6 +1691,8 @@ :identifier: catalogitem_edit - :name: delete :identifier: catalogitem_delete + - :name: create + :identifier: catalogitem_new :subcollection_actions: :post: - :name: edit @@ -1712,6 +1714,8 @@ :post: - :name: edit :identifier: catalogitem_edit + - :name: create + :identifier: catalogitem_new :delete: - :name: delete :identifier: catalogitem_delete diff --git a/spec/requests/api/service_templates_spec.rb b/spec/requests/api/service_templates_spec.rb index f11a323224f..c4cb006bd10 100644 --- a/spec/requests/api/service_templates_spec.rb +++ b/spec/requests/api/service_templates_spec.rb @@ -210,4 +210,55 @@ expect(response).to have_http_status(:ok) end end + + describe "Service Templates create" do + it 'rejects requests without appropriate role' do + api_basic_authorize + + run_post(service_templates_url, :name => 'foobar') + + expect(response).to have_http_status(:forbidden) + end + + it 'can create a single service template' do + api_basic_authorize action_identifier(:service_templates, :create) + + run_post(service_templates_url, :name => 'foobar') + + expected = { + 'results' => [ + a_hash_including( + 'name' => 'foobar' + ) + ] + } + expect do + run_post(service_templates_url, :name => 'foobar') + end.to change(ServiceTemplate, :count).by(1) + expect(response).to have_http_status(:ok) + expect(response.parsed_body).to include(expected) + end + + it 'can create multiple service templates' do + api_basic_authorize action_identifier(:service_templates, :create) + + expected = { + 'results' => a_collection_including( + a_hash_including( + 'name' => 'foo' + ), + a_hash_including( + 'name' => 'bar' + ) + ) + } + expect do + run_post(service_templates_url, :action => 'create', :resources => [ + { :name => 'foo' }, { :name => 'bar'} + ]) + end.to change(ServiceTemplate, :count).by(2) + expect(response).to have_http_status(:ok) + expect(response.parsed_body).to include(expected) + end + end end From 3882e295673f8375836fb77906b664bcae1e248b Mon Sep 17 00:00:00 2001 From: Jillian Tullo Date: Wed, 16 Nov 2016 14:20:03 -0500 Subject: [PATCH 2/9] ability to create atomic service templates --- spec/requests/api/service_templates_spec.rb | 56 ++++++++++++++++----- 1 file changed, 44 insertions(+), 12 deletions(-) diff --git a/spec/requests/api/service_templates_spec.rb b/spec/requests/api/service_templates_spec.rb index c4cb006bd10..60093034080 100644 --- a/spec/requests/api/service_templates_spec.rb +++ b/spec/requests/api/service_templates_spec.rb @@ -211,7 +211,33 @@ end end - describe "Service Templates create" do + describe "Atomic Service Templates create" do + let(:ems) { FactoryGirl.create(:ems_amazon) } + let(:vm) { FactoryGirl.create(:vm_amazon, :ems_id => ems.id) } + let(:flavor) { FactoryGirl.create(:flavor_amazon) } + let(:dialog) { FactoryGirl.create(:miq_dialog_provision) } + let(:template_parameters) do + { + :name => 'Atomic Service Template', + :type => 'ServiceTemplate', + :service_type => 'atomic', + :prov_type => 'amazon', + :display => 'false', + :request_info => { + :miq_request_dialog_name => dialog.name, + :placement_auto => [true, 1], + :number_of_vms => [1, '1'], + :src_vm_id => [vm.id, vm.name], + :vm_name => 'AtomicVMName', + :schedule_type => ["immediately", "Immediately on Approval"], + :instance_type => [flavor.id, flavor.name], + :retire_fqname => ra2.fqname, + :fqname => ra1.fqname, + :src_ems_id => [ems.id, ems.name] + } + } + end + it 'rejects requests without appropriate role' do api_basic_authorize @@ -223,17 +249,19 @@ it 'can create a single service template' do api_basic_authorize action_identifier(:service_templates, :create) - run_post(service_templates_url, :name => 'foobar') - expected = { - 'results' => [ + 'results' => a_collection_including( a_hash_including( - 'name' => 'foobar' + 'name' => 'Atomic Service Template', + 'display' => false, + 'service_type' => 'atomic', + 'prov_type' => 'amazon' ) - ] + ) } + expect do - run_post(service_templates_url, :name => 'foobar') + run_post(service_templates_url, template_parameters) end.to change(ServiceTemplate, :count).by(1) expect(response).to have_http_status(:ok) expect(response.parsed_body).to include(expected) @@ -242,20 +270,24 @@ it 'can create multiple service templates' do api_basic_authorize action_identifier(:service_templates, :create) + template_hash = { + 'name' => 'Atomic Service Template', + 'display' => false, + 'service_type' => 'atomic', + 'prov_type' => 'amazon' + } expected = { 'results' => a_collection_including( a_hash_including( - 'name' => 'foo' + template_hash ), a_hash_including( - 'name' => 'bar' + template_hash ) ) } expect do - run_post(service_templates_url, :action => 'create', :resources => [ - { :name => 'foo' }, { :name => 'bar'} - ]) + run_post(service_templates_url, :action => 'create', :resources => [template_parameters, template_parameters]) end.to change(ServiceTemplate, :count).by(2) expect(response).to have_http_status(:ok) expect(response.parsed_body).to include(expected) From 9f409ba636a5dea40035dc420afd8fe9930f4022 Mon Sep 17 00:00:00 2001 From: Jillian Tullo Date: Thu, 17 Nov 2016 09:57:15 -0500 Subject: [PATCH 3/9] additional validations and specs --- .../api/service_templates_controller.rb | 49 +++++++++++++++++++ spec/requests/api/service_templates_spec.rb | 48 ++++++++++++++++++ 2 files changed, 97 insertions(+) diff --git a/app/controllers/api/service_templates_controller.rb b/app/controllers/api/service_templates_controller.rb index 9349793e754..da6fbebdcba 100644 --- a/app/controllers/api/service_templates_controller.rb +++ b/app/controllers/api/service_templates_controller.rb @@ -7,10 +7,59 @@ class ServiceTemplatesController < BaseController before_action :set_additional_attributes, :only => [:show] + def create_resource(_type, _id, data) + # Temporarily only supporting atomic. + # Will update API to support composite separately. + raise 'Service Type composite not supported' if data['service_type'] == 'composite' + create_atomic(data).tap(&:save!) + rescue => err + raise BadRequestError, "Could not create Service Template - #{err}" + end + private def set_additional_attributes @additional_attributes = %w(config_info) end + + def create_atomic(data) + validate_atomic_data(data) + service_template = ServiceTemplate.new(data.except('request_info')) + service_template.add_resource(create_service_template_request(data)) + set_new_resource_actions(data['request_info'], service_template) + end + + def validate_atomic_data(data) + raise 'Must provide request info' unless data.key?('request_info') + raise 'Provisioning Entry Point is required' unless data['request_info']['fqname'] + raise 'Source VM is required' unless data['request_info']['src_vm_id'] + end + + # Need to set the request for non-generic Service Template + def create_service_template_request(data) + # hash must be passed as symbols + request_params = data['request_info'].symbolize_keys + wf = MiqProvisionWorkflow.class_for_source(data['request_info']['src_vm_id']).new(request_params, @auth_user_obj) + raise 'Could not find Provision Workflow class for source VM' unless wf + request = wf.make_request(nil, request_params) + raise 'Could not create valid request' if request == false || !request.valid? + request + end + + # Set Resource Actions + def set_new_resource_actions(data, service_template) + dialog = data['dialog_id'].nil? ? nil : Dialog.find(data['dialog_id']) + [ + {:name => 'Provision', :params_key => 'fqname'}, + {:name => 'Reconfigure', :params_key => 'reconfigure_fqname'}, + {:name => 'Retirement', :params_key => 'retire_fqname'} + ].each do |action| + unless data[action[:params_key]].nil? + ra = service_template.resource_actions.build(:action => action[:name]) + ra.update_attributes(:dialog => dialog, :fqname => data[action[:params_key]]) + end + end + service_template + end end end diff --git a/spec/requests/api/service_templates_spec.rb b/spec/requests/api/service_templates_spec.rb index 60093034080..cf330667229 100644 --- a/spec/requests/api/service_templates_spec.rb +++ b/spec/requests/api/service_templates_spec.rb @@ -292,5 +292,53 @@ expect(response).to have_http_status(:ok) expect(response.parsed_body).to include(expected) end + + it 'requires request_info' do + api_basic_authorize action_identifier(:service_templates, :create) + + expected = { + 'error' => a_hash_including( + 'kind' => 'bad_request', + 'message' => 'Could not create Service Template - Must provide request info' + ) + } + run_post(service_templates_url, template_parameters.except(:request_info)) + + expect(response).to have_http_status(:bad_request) + expect(response.parsed_body).to include(expected) + end + + it 'requires a Provisioning Entrypoint' do + api_basic_authorize action_identifier(:service_templates, :create) + template_parameters[:request_info].delete(:fqname) + + expected = { + 'error' => + a_hash_including( + 'kind' => 'bad_request', + 'message' => 'Could not create Service Template - Provisioning Entry Point is required' + ) + } + run_post(service_templates_url, template_parameters) + + expect(response).to have_http_status(:bad_request) + expect(response.parsed_body).to include(expected) + end + + it 'requires a source VM' do + api_basic_authorize action_identifier(:service_templates, :create) + template_parameters[:request_info].delete(:src_vm_id) + + expected = { + 'error' => a_hash_including( + 'kind' => 'bad_request', + 'message' => 'Could not create Service Template - Source VM is required' + ) + } + run_post(service_templates_url, template_parameters) + + expect(response).to have_http_status(:bad_request) + expect(response.parsed_body).to include(expected) + end end end From 25e3d833aee1dfde99ff4c9199dbfcf120460f95 Mon Sep 17 00:00:00 2001 From: Jillian Tullo Date: Fri, 18 Nov 2016 10:59:39 -0500 Subject: [PATCH 4/9] removing validations remove resource action always set entry point --- .../api/service_templates_controller.rb | 57 +++++++++++-------- config/api.yml | 2 - spec/requests/api/service_templates_spec.rb | 57 +++++-------------- 3 files changed, 46 insertions(+), 70 deletions(-) diff --git a/app/controllers/api/service_templates_controller.rb b/app/controllers/api/service_templates_controller.rb index da6fbebdcba..733aab5ca02 100644 --- a/app/controllers/api/service_templates_controller.rb +++ b/app/controllers/api/service_templates_controller.rb @@ -9,7 +9,6 @@ class ServiceTemplatesController < BaseController def create_resource(_type, _id, data) # Temporarily only supporting atomic. - # Will update API to support composite separately. raise 'Service Type composite not supported' if data['service_type'] == 'composite' create_atomic(data).tap(&:save!) rescue => err @@ -23,43 +22,51 @@ def set_additional_attributes end def create_atomic(data) - validate_atomic_data(data) service_template = ServiceTemplate.new(data.except('request_info')) - service_template.add_resource(create_service_template_request(data)) - set_new_resource_actions(data['request_info'], service_template) - end - - def validate_atomic_data(data) - raise 'Must provide request info' unless data.key?('request_info') - raise 'Provisioning Entry Point is required' unless data['request_info']['fqname'] - raise 'Source VM is required' unless data['request_info']['src_vm_id'] + dialog = nil + if data.key?('request_info') + service_template.add_resource(create_service_template_request(data['request_info'])) + dialog = Dialog.find(data['request_info']['dialog_id']) if data['request_info']['dialog_id'] + end + set_provision_action(service_template, dialog, data['request_info']) + set_retirement_reconfigure_action(service_template, dialog, data['request_info']) + service_template end # Need to set the request for non-generic Service Template - def create_service_template_request(data) - # hash must be passed as symbols - request_params = data['request_info'].symbolize_keys - wf = MiqProvisionWorkflow.class_for_source(data['request_info']['src_vm_id']).new(request_params, @auth_user_obj) + def create_service_template_request(request_data) + # data must be symbolized + request_params = request_data.symbolize_keys + wf = MiqProvisionWorkflow.class_for_source(request_params[:src_vm_id]).new(request_params, @auth_user_obj) raise 'Could not find Provision Workflow class for source VM' unless wf request = wf.make_request(nil, request_params) raise 'Could not create valid request' if request == false || !request.valid? request end - # Set Resource Actions - def set_new_resource_actions(data, service_template) - dialog = data['dialog_id'].nil? ? nil : Dialog.find(data['dialog_id']) + def set_provision_action(service_template, dialog, request_info) + fqname = if request_info && request_info['fqname'] + request_info['fqname'] + else + service_template.class.default_provisioning_entry_point(service_template.service_type) + end + ra = service_template.resource_actions.build(:action => 'Provision') + ra.update_attributes(:dialog => dialog, :fqname => fqname) + end + + def set_retirement_reconfigure_action(service_template, dialog, request_info) [ - {:name => 'Provision', :params_key => 'fqname'}, - {:name => 'Reconfigure', :params_key => 'reconfigure_fqname'}, - {:name => 'Retirement', :params_key => 'retire_fqname'} + {:name => 'Reconfigure', :param_key => 'reconfigure_fqname', :method => 'default_reconfiguration_entry_point'}, + {:name => 'Retirement', :param_key => 'retire_fqname', :method => 'default_retirement_entry_point'} ].each do |action| - unless data[action[:params_key]].nil? - ra = service_template.resource_actions.build(:action => action[:name]) - ra.update_attributes(:dialog => dialog, :fqname => data[action[:params_key]]) - end + ra = service_template.resource_actions.build(:action => action[:name], :dialog => dialog) + fqname = if request_info && request_info[action[:param_key]] + request_info[action[:param_key]] + else + service_template.class.send(action[:method]) + end + ra.update_attributes(:fqname => fqname) if fqname end - service_template end end end diff --git a/config/api.yml b/config/api.yml index 723f222c0a5..085718d5055 100644 --- a/config/api.yml +++ b/config/api.yml @@ -1714,8 +1714,6 @@ :post: - :name: edit :identifier: catalogitem_edit - - :name: create - :identifier: catalogitem_new :delete: - :name: delete :identifier: catalogitem_delete diff --git a/spec/requests/api/service_templates_spec.rb b/spec/requests/api/service_templates_spec.rb index cf330667229..798bacc0c8e 100644 --- a/spec/requests/api/service_templates_spec.rb +++ b/spec/requests/api/service_templates_spec.rb @@ -247,7 +247,7 @@ end it 'can create a single service template' do - api_basic_authorize action_identifier(:service_templates, :create) + api_basic_authorize collection_action_identifier(:service_templates, :create) expected = { 'results' => a_collection_including( @@ -268,7 +268,7 @@ end it 'can create multiple service templates' do - api_basic_authorize action_identifier(:service_templates, :create) + api_basic_authorize collection_action_identifier(:service_templates, :create) template_hash = { 'name' => 'Atomic Service Template', @@ -293,51 +293,22 @@ expect(response.parsed_body).to include(expected) end - it 'requires request_info' do - api_basic_authorize action_identifier(:service_templates, :create) - - expected = { - 'error' => a_hash_including( - 'kind' => 'bad_request', - 'message' => 'Could not create Service Template - Must provide request info' - ) + it 'can create a generic service template' do + api_basic_authorize collection_action_identifier(:service_templates, :create) + template_params = { + 'name' => 'Generic Service Template', + 'service_type' => 'atomic', + 'prov_type' => 'generic', + 'generic_subtype' => '' } - run_post(service_templates_url, template_parameters.except(:request_info)) - - expect(response).to have_http_status(:bad_request) - expect(response.parsed_body).to include(expected) - end - - it 'requires a Provisioning Entrypoint' do - api_basic_authorize action_identifier(:service_templates, :create) - template_parameters[:request_info].delete(:fqname) expected = { - 'error' => - a_hash_including( - 'kind' => 'bad_request', - 'message' => 'Could not create Service Template - Provisioning Entry Point is required' - ) + 'results' => [a_hash_including(template_params)] } - run_post(service_templates_url, template_parameters) - - expect(response).to have_http_status(:bad_request) - expect(response.parsed_body).to include(expected) - end - - it 'requires a source VM' do - api_basic_authorize action_identifier(:service_templates, :create) - template_parameters[:request_info].delete(:src_vm_id) - - expected = { - 'error' => a_hash_including( - 'kind' => 'bad_request', - 'message' => 'Could not create Service Template - Source VM is required' - ) - } - run_post(service_templates_url, template_parameters) - - expect(response).to have_http_status(:bad_request) + expect do + run_post(service_templates_url, template_params) + end.to change(ServiceTemplate, :count).by(1) + expect(response).to have_http_status(:ok) expect(response.parsed_body).to include(expected) end end From 916c5ec474e235657ccb5fcf6f2c0d18f243bd56 Mon Sep 17 00:00:00 2001 From: Jillian Tullo Date: Mon, 21 Nov 2016 15:55:28 -0500 Subject: [PATCH 5/9] add support for generic service template orchestration moving some methods to the model --- .../api/service_templates_controller.rb | 50 ++++++++----------- app/models/service_template.rb | 25 ++++++++++ spec/models/service_template_spec.rb | 30 +++++++++++ spec/requests/api/service_templates_spec.rb | 50 ++++++++++++++++++- 4 files changed, 126 insertions(+), 29 deletions(-) diff --git a/app/controllers/api/service_templates_controller.rb b/app/controllers/api/service_templates_controller.rb index 733aab5ca02..856310a0eb2 100644 --- a/app/controllers/api/service_templates_controller.rb +++ b/app/controllers/api/service_templates_controller.rb @@ -22,14 +22,18 @@ def set_additional_attributes end def create_atomic(data) - service_template = ServiceTemplate.new(data.except('request_info')) - dialog = nil - if data.key?('request_info') - service_template.add_resource(create_service_template_request(data['request_info'])) - dialog = Dialog.find(data['request_info']['dialog_id']) if data['request_info']['dialog_id'] + service_template = ServiceTemplate.new(data.except('config_info')) + config_info = data['config_info'].nil? ? {} : data['config_info'] + case service_template.type + when 'ServiceTemplateOrchestration' + add_orchestration_template_vars(service_template, config_info) + when 'ServiceTemplateAnsibleTower' + add_ansible_tower_job_template_vars(service_template, config_info) + else + service_template.add_resource(create_service_template_request(config_info)) unless config_info == {} end - set_provision_action(service_template, dialog, data['request_info']) - set_retirement_reconfigure_action(service_template, dialog, data['request_info']) + dialog = Dialog.find(config_info['dialog_id']) if config_info['dialog_id'] + service_template.set_resource_actions(config_info, dialog) service_template end @@ -44,29 +48,19 @@ def create_service_template_request(request_data) request end - def set_provision_action(service_template, dialog, request_info) - fqname = if request_info && request_info['fqname'] - request_info['fqname'] - else - service_template.class.default_provisioning_entry_point(service_template.service_type) - end - ra = service_template.resource_actions.build(:action => 'Provision') - ra.update_attributes(:dialog => dialog, :fqname => fqname) + def add_orchestration_template_vars(service_template, config_info) + service_template.orchestration_template = unless config_info['template_id'].nil? + OrchestrationTemplate.find(config_info['template_id']) + end + service_template.orchestration_manager = unless config_info['manager_id'].nil? + ExtManagementSystem.find(config_info['manager_id']) + end end - def set_retirement_reconfigure_action(service_template, dialog, request_info) - [ - {:name => 'Reconfigure', :param_key => 'reconfigure_fqname', :method => 'default_reconfiguration_entry_point'}, - {:name => 'Retirement', :param_key => 'retire_fqname', :method => 'default_retirement_entry_point'} - ].each do |action| - ra = service_template.resource_actions.build(:action => action[:name], :dialog => dialog) - fqname = if request_info && request_info[action[:param_key]] - request_info[action[:param_key]] - else - service_template.class.send(action[:method]) - end - ra.update_attributes(:fqname => fqname) if fqname - end + def add_ansible_tower_job_template_vars(service_template, config_info) + service_template.job_template = unless config_info['template_id'].nil? + ConfigurationScript.find(config_info['template_id']) + end end end end diff --git a/app/models/service_template.rb b/app/models/service_template.rb index 381d3df3deb..d0acb3a2321 100644 --- a/app/models/service_template.rb +++ b/app/models/service_template.rb @@ -296,6 +296,31 @@ def self.default_reconfiguration_entry_point nil end + def set_resource_actions(config_info, dialog) + [ + {:name => 'Provision', + :param_key => 'fqname', + :method => 'default_provisioning_entry_point', + :args => [service_type]}, + {:name => 'Reconfigure', + :param_key => 'reconfigure_fqname', + :method => 'default_reconfiguration_entry_point', + :args => []}, + {:name => 'Retirement', + :param_key => 'retire_fqname', + :method => 'default_retirement_entry_point', + :args => []} + ].each do |action| + fqname = if config_info[action[:param_key]].nil? + self.class.send(action[:method], *action[:args]) || "" + else + config_info[action[:param_key]] + end + resource_actions.build(:action => action[:name], :fqname => fqname, :dialog => dialog) + end + save! + end + def template_valid? validate_template[:valid] end diff --git a/spec/models/service_template_spec.rb b/spec/models/service_template_spec.rb index 07724057ff9..4397ef88e5a 100644 --- a/spec/models/service_template_spec.rb +++ b/spec/models/service_template_spec.rb @@ -83,6 +83,36 @@ end end + context '#set_resource_actions' do + before do + @service_template = FactoryGirl.create(:service_template) + @dialog = FactoryGirl.create(:dialog) + end + + it 'uses default resource actions' do + expect(ServiceTemplate).to receive(:default_provisioning_entry_point) + .with(@service_template.service_type).and_return('default') + expect(ServiceTemplate).to receive(:default_retirement_entry_point) + expect(ServiceTemplate).to receive(:default_reconfiguration_entry_point) + + @service_template.set_resource_actions({}, @dialog) + + expect(@service_template.resource_actions.count).to eq(3) + end + + it 'uses selected resource actions' do + config_info = { + 'fqname' => 'foo_entrypoint', + 'reconfigure_fqname' => 'foo_reconfigure', + 'retire_fqname' => 'foo_retire' + } + + @service_template.set_resource_actions(config_info, @dialog) + expect(@service_template.resource_actions.pluck(:ae_instance)) + .to eq(%w(foo_entrypoint foo_reconfigure foo_retire)) + end + end + context "#atomic?" do before(:each) do @st1 = FactoryGirl.create(:service_template) diff --git a/spec/requests/api/service_templates_spec.rb b/spec/requests/api/service_templates_spec.rb index 798bacc0c8e..3d856462708 100644 --- a/spec/requests/api/service_templates_spec.rb +++ b/spec/requests/api/service_templates_spec.rb @@ -223,7 +223,7 @@ :service_type => 'atomic', :prov_type => 'amazon', :display => 'false', - :request_info => { + :config_info => { :miq_request_dialog_name => dialog.name, :placement_auto => [true, 1], :number_of_vms => [1, '1'], @@ -311,5 +311,53 @@ expect(response).to have_http_status(:ok) expect(response.parsed_body).to include(expected) end + + it 'can create a ServiceTemplateOrchestration' do + api_basic_authorize collection_action_identifier(:service_templates, :create) + orchestration_template = FactoryGirl.create(:orchestration_template) + ems = FactoryGirl.create(:ext_management_system) + template_params = { + 'name' => 'Orchestration Template', + 'type' => 'ServiceTemplateOrchestration', + 'prov_type' => 'generic_orchestration', + 'service_type' => 'atomic', + 'config_info' => { + 'template_id' => orchestration_template.id, + 'manager_id' => ems.id + } + } + + expected = { + 'results' => [a_hash_including(template_params.except('config_info'))] + } + expect do + run_post(service_templates_url, template_params) + end.to change(ServiceTemplateOrchestration, :count).by(1) + expect(response).to have_http_status(:ok) + expect(response.parsed_body).to include(expected) + end + + it 'can create a ServiceTemplateAnsibleTower' do + api_basic_authorize collection_action_identifier(:service_templates, :create) + config_script = FactoryGirl.create(:configuration_script) + template_params = { + 'name' => 'Ansible Tower', + 'type' => 'ServiceTemplateAnsibleTower', + 'prov_type' => 'generic_ansible_tower', + 'service_type' => 'atomic', + 'config_info' => { + 'template_id' => config_script.id + } + } + + expected = { + 'results' => [a_hash_including(template_params.except('config_info'))] + } + expect do + run_post(service_templates_url, template_params) + end.to change(ServiceTemplateAnsibleTower, :count).by(1) + expect(response).to have_http_status(:ok) + expect(response.parsed_body).to include(expected) + end end end From 0d10632090aa85a3aeb03ca33dcd99d693baf7b9 Mon Sep 17 00:00:00 2001 From: Jillian Tullo Date: Thu, 5 Jan 2017 13:51:30 -0500 Subject: [PATCH 6/9] updating config_info to include ae_endpoints --- .../api/service_templates_controller.rb | 6 +++--- app/models/service_template.rb | 13 +++++++------ spec/models/service_template_spec.rb | 10 +++++----- spec/requests/api/service_templates_spec.rb | 17 ++++++++++------- 4 files changed, 25 insertions(+), 21 deletions(-) diff --git a/app/controllers/api/service_templates_controller.rb b/app/controllers/api/service_templates_controller.rb index 856310a0eb2..3c0f2a8f30c 100644 --- a/app/controllers/api/service_templates_controller.rb +++ b/app/controllers/api/service_templates_controller.rb @@ -23,7 +23,7 @@ def set_additional_attributes def create_atomic(data) service_template = ServiceTemplate.new(data.except('config_info')) - config_info = data['config_info'].nil? ? {} : data['config_info'] + config_info = data['config_info'].except('ae_endpoints') || {} case service_template.type when 'ServiceTemplateOrchestration' add_orchestration_template_vars(service_template, config_info) @@ -33,7 +33,7 @@ def create_atomic(data) service_template.add_resource(create_service_template_request(config_info)) unless config_info == {} end dialog = Dialog.find(config_info['dialog_id']) if config_info['dialog_id'] - service_template.set_resource_actions(config_info, dialog) + service_template.set_resource_actions(config_info['ae_endpoints'], dialog) service_template end @@ -41,7 +41,7 @@ def create_atomic(data) def create_service_template_request(request_data) # data must be symbolized request_params = request_data.symbolize_keys - wf = MiqProvisionWorkflow.class_for_source(request_params[:src_vm_id]).new(request_params, @auth_user_obj) + wf = MiqProvisionWorkflow.class_for_source(request_params[:src_vm_id]).new(request_params, @auth_user) raise 'Could not find Provision Workflow class for source VM' unless wf request = wf.make_request(nil, request_params) raise 'Could not create valid request' if request == false || !request.valid? diff --git a/app/models/service_template.rb b/app/models/service_template.rb index d0acb3a2321..ec47c766b7b 100644 --- a/app/models/service_template.rb +++ b/app/models/service_template.rb @@ -296,25 +296,26 @@ def self.default_reconfiguration_entry_point nil end - def set_resource_actions(config_info, dialog) + def set_resource_actions(ae_endpoints, dialog) + ae_endpoints ||= {} [ {:name => 'Provision', - :param_key => 'fqname', + :param_key => 'provisioning', :method => 'default_provisioning_entry_point', :args => [service_type]}, {:name => 'Reconfigure', - :param_key => 'reconfigure_fqname', + :param_key => 'reconfigure', :method => 'default_reconfiguration_entry_point', :args => []}, {:name => 'Retirement', - :param_key => 'retire_fqname', + :param_key => 'retirement', :method => 'default_retirement_entry_point', :args => []} ].each do |action| - fqname = if config_info[action[:param_key]].nil? + fqname = if ae_endpoints[action[:param_key]].nil? self.class.send(action[:method], *action[:args]) || "" else - config_info[action[:param_key]] + ae_endpoints[action[:param_key]] end resource_actions.build(:action => action[:name], :fqname => fqname, :dialog => dialog) end diff --git a/spec/models/service_template_spec.rb b/spec/models/service_template_spec.rb index 4397ef88e5a..2b01eb7519f 100644 --- a/spec/models/service_template_spec.rb +++ b/spec/models/service_template_spec.rb @@ -101,13 +101,13 @@ end it 'uses selected resource actions' do - config_info = { - 'fqname' => 'foo_entrypoint', - 'reconfigure_fqname' => 'foo_reconfigure', - 'retire_fqname' => 'foo_retire' + ae_endpoints = { + 'provisioning' => 'foo_entrypoint', + 'reconfigure' => 'foo_reconfigure', + 'retirement' => 'foo_retire' } - @service_template.set_resource_actions(config_info, @dialog) + @service_template.set_resource_actions(ae_endpoints, @dialog) expect(@service_template.resource_actions.pluck(:ae_instance)) .to eq(%w(foo_entrypoint foo_reconfigure foo_retire)) end diff --git a/spec/requests/api/service_templates_spec.rb b/spec/requests/api/service_templates_spec.rb index 3d856462708..09c688ac2af 100644 --- a/spec/requests/api/service_templates_spec.rb +++ b/spec/requests/api/service_templates_spec.rb @@ -231,9 +231,11 @@ :vm_name => 'AtomicVMName', :schedule_type => ["immediately", "Immediately on Approval"], :instance_type => [flavor.id, flavor.name], - :retire_fqname => ra2.fqname, - :fqname => ra1.fqname, - :src_ems_id => [ems.id, ems.name] + :src_ems_id => [ems.id, ems.name], + :ae_endpoints => { + :provisioning => ra1.fqname, + :retirement => ra2.fqname + } } } end @@ -299,11 +301,12 @@ 'name' => 'Generic Service Template', 'service_type' => 'atomic', 'prov_type' => 'generic', - 'generic_subtype' => '' + 'generic_subtype' => '', + 'config_info' => {} } expected = { - 'results' => [a_hash_including(template_params)] + 'results' => [a_hash_including(template_params.except('config_info'))] } expect do run_post(service_templates_url, template_params) @@ -323,7 +326,7 @@ 'service_type' => 'atomic', 'config_info' => { 'template_id' => orchestration_template.id, - 'manager_id' => ems.id + 'manager_id' => ems.id, } } @@ -346,7 +349,7 @@ 'prov_type' => 'generic_ansible_tower', 'service_type' => 'atomic', 'config_info' => { - 'template_id' => config_script.id + 'template_id' => config_script.id, } } From 8e807e14de03d2858c0a84d253870369782b3ab8 Mon Sep 17 00:00:00 2001 From: Jillian Tullo Date: Thu, 26 Jan 2017 11:19:09 -0500 Subject: [PATCH 7/9] use create_catalog_item default to ServiceTemplate --- .../api/service_templates_controller.rb | 49 +--------- app/models/service_template.rb | 28 +----- spec/models/service_template_spec.rb | 30 ------ spec/requests/api/service_templates_spec.rb | 96 +++++++------------ 4 files changed, 38 insertions(+), 165 deletions(-) diff --git a/app/controllers/api/service_templates_controller.rb b/app/controllers/api/service_templates_controller.rb index 3c0f2a8f30c..2abde4c19bf 100644 --- a/app/controllers/api/service_templates_controller.rb +++ b/app/controllers/api/service_templates_controller.rb @@ -7,10 +7,9 @@ class ServiceTemplatesController < BaseController before_action :set_additional_attributes, :only => [:show] - def create_resource(_type, _id, data) - # Temporarily only supporting atomic. - raise 'Service Type composite not supported' if data['service_type'] == 'composite' - create_atomic(data).tap(&:save!) + def create_resource(type, _id, data) + catalog_item_type = data['type'].try(:constantize) || collection_class(type) + catalog_item_type.create_catalog_item(data.deep_symbolize_keys, @auth_user) rescue => err raise BadRequestError, "Could not create Service Template - #{err}" end @@ -20,47 +19,5 @@ def create_resource(_type, _id, data) def set_additional_attributes @additional_attributes = %w(config_info) end - - def create_atomic(data) - service_template = ServiceTemplate.new(data.except('config_info')) - config_info = data['config_info'].except('ae_endpoints') || {} - case service_template.type - when 'ServiceTemplateOrchestration' - add_orchestration_template_vars(service_template, config_info) - when 'ServiceTemplateAnsibleTower' - add_ansible_tower_job_template_vars(service_template, config_info) - else - service_template.add_resource(create_service_template_request(config_info)) unless config_info == {} - end - dialog = Dialog.find(config_info['dialog_id']) if config_info['dialog_id'] - service_template.set_resource_actions(config_info['ae_endpoints'], dialog) - service_template - end - - # Need to set the request for non-generic Service Template - def create_service_template_request(request_data) - # data must be symbolized - request_params = request_data.symbolize_keys - wf = MiqProvisionWorkflow.class_for_source(request_params[:src_vm_id]).new(request_params, @auth_user) - raise 'Could not find Provision Workflow class for source VM' unless wf - request = wf.make_request(nil, request_params) - raise 'Could not create valid request' if request == false || !request.valid? - request - end - - def add_orchestration_template_vars(service_template, config_info) - service_template.orchestration_template = unless config_info['template_id'].nil? - OrchestrationTemplate.find(config_info['template_id']) - end - service_template.orchestration_manager = unless config_info['manager_id'].nil? - ExtManagementSystem.find(config_info['manager_id']) - end - end - - def add_ansible_tower_job_template_vars(service_template, config_info) - service_template.job_template = unless config_info['template_id'].nil? - ConfigurationScript.find(config_info['template_id']) - end - end end end diff --git a/app/models/service_template.rb b/app/models/service_template.rb index ec47c766b7b..aa8ac76866a 100644 --- a/app/models/service_template.rb +++ b/app/models/service_template.rb @@ -296,36 +296,10 @@ def self.default_reconfiguration_entry_point nil end - def set_resource_actions(ae_endpoints, dialog) - ae_endpoints ||= {} - [ - {:name => 'Provision', - :param_key => 'provisioning', - :method => 'default_provisioning_entry_point', - :args => [service_type]}, - {:name => 'Reconfigure', - :param_key => 'reconfigure', - :method => 'default_reconfiguration_entry_point', - :args => []}, - {:name => 'Retirement', - :param_key => 'retirement', - :method => 'default_retirement_entry_point', - :args => []} - ].each do |action| - fqname = if ae_endpoints[action[:param_key]].nil? - self.class.send(action[:method], *action[:args]) || "" - else - ae_endpoints[action[:param_key]] - end - resource_actions.build(:action => action[:name], :fqname => fqname, :dialog => dialog) - end - save! - end - def template_valid? validate_template[:valid] end - alias template_valid template_valid? + alias_method :template_valid, :template_valid? def template_valid_error_message validate_template[:message] diff --git a/spec/models/service_template_spec.rb b/spec/models/service_template_spec.rb index 2b01eb7519f..07724057ff9 100644 --- a/spec/models/service_template_spec.rb +++ b/spec/models/service_template_spec.rb @@ -83,36 +83,6 @@ end end - context '#set_resource_actions' do - before do - @service_template = FactoryGirl.create(:service_template) - @dialog = FactoryGirl.create(:dialog) - end - - it 'uses default resource actions' do - expect(ServiceTemplate).to receive(:default_provisioning_entry_point) - .with(@service_template.service_type).and_return('default') - expect(ServiceTemplate).to receive(:default_retirement_entry_point) - expect(ServiceTemplate).to receive(:default_reconfiguration_entry_point) - - @service_template.set_resource_actions({}, @dialog) - - expect(@service_template.resource_actions.count).to eq(3) - end - - it 'uses selected resource actions' do - ae_endpoints = { - 'provisioning' => 'foo_entrypoint', - 'reconfigure' => 'foo_reconfigure', - 'retirement' => 'foo_retire' - } - - @service_template.set_resource_actions(ae_endpoints, @dialog) - expect(@service_template.resource_actions.pluck(:ae_instance)) - .to eq(%w(foo_entrypoint foo_reconfigure foo_retire)) - end - end - context "#atomic?" do before(:each) do @st1 = FactoryGirl.create(:service_template) diff --git a/spec/requests/api/service_templates_spec.rb b/spec/requests/api/service_templates_spec.rb index 09c688ac2af..bcfdf44c058 100644 --- a/spec/requests/api/service_templates_spec.rb +++ b/spec/requests/api/service_templates_spec.rb @@ -211,15 +211,15 @@ end end - describe "Atomic Service Templates create" do + describe "Service Templates create" do let(:ems) { FactoryGirl.create(:ems_amazon) } let(:vm) { FactoryGirl.create(:vm_amazon, :ems_id => ems.id) } let(:flavor) { FactoryGirl.create(:flavor_amazon) } let(:dialog) { FactoryGirl.create(:miq_dialog_provision) } + let(:service_dialog) { FactoryGirl.create(:dialog) } let(:template_parameters) do { :name => 'Atomic Service Template', - :type => 'ServiceTemplate', :service_type => 'atomic', :prov_type => 'amazon', :display => 'false', @@ -232,9 +232,13 @@ :schedule_type => ["immediately", "Immediately on Approval"], :instance_type => [flavor.id, flavor.name], :src_ems_id => [ems.id, ems.name], - :ae_endpoints => { - :provisioning => ra1.fqname, - :retirement => ra2.fqname + :provision => { + :fqname => ra1.fqname, + :dialog_id => service_dialog.id + }, + :retirement => { + :fqname => ra2.fqname, + :dialog_id => service_dialog.id } } } @@ -248,7 +252,7 @@ expect(response).to have_http_status(:forbidden) end - it 'can create a single service template' do + it 'can create a single service template ' do api_basic_authorize collection_action_identifier(:service_templates, :create) expected = { @@ -295,72 +299,40 @@ expect(response.parsed_body).to include(expected) end - it 'can create a generic service template' do + it 'can create other resource types' do api_basic_authorize collection_action_identifier(:service_templates, :create) - template_params = { - 'name' => 'Generic Service Template', - 'service_type' => 'atomic', - 'prov_type' => 'generic', - 'generic_subtype' => '', - 'config_info' => {} - } - - expected = { - 'results' => [a_hash_including(template_params.except('config_info'))] - } - expect do - run_post(service_templates_url, template_params) - end.to change(ServiceTemplate, :count).by(1) - expect(response).to have_http_status(:ok) - expect(response.parsed_body).to include(expected) - end - - it 'can create a ServiceTemplateOrchestration' do - api_basic_authorize collection_action_identifier(:service_templates, :create) - orchestration_template = FactoryGirl.create(:orchestration_template) - ems = FactoryGirl.create(:ext_management_system) - template_params = { - 'name' => 'Orchestration Template', - 'type' => 'ServiceTemplateOrchestration', - 'prov_type' => 'generic_orchestration', - 'service_type' => 'atomic', - 'config_info' => { - 'template_id' => orchestration_template.id, - 'manager_id' => ems.id, + template = FactoryGirl.create(:orchestration_template) + template_parameters = { + :type => 'ServiceTemplateOrchestration', + :name => 'Orchestration Template', + :service_type => 'atomic', + :prov_type => 'generic_orchestration', + :display => 'false', + :description => 'a description', + :config_info => { + :template_id => template.id, + :manager_id => ems.id, + :provision => { + :fqname => ra1.fqname, + :dialog_id => service_dialog.id + }, + :retirement => { + :fqname => ra2.fqname, + :dialog_id => service_dialog.id + } } } expected = { - 'results' => [a_hash_including(template_params.except('config_info'))] + 'results' => [a_hash_including( + 'type' => 'ServiceTemplateOrchestration' + )] } expect do - run_post(service_templates_url, template_params) + run_post(service_templates_url, template_parameters) end.to change(ServiceTemplateOrchestration, :count).by(1) expect(response).to have_http_status(:ok) expect(response.parsed_body).to include(expected) end - - it 'can create a ServiceTemplateAnsibleTower' do - api_basic_authorize collection_action_identifier(:service_templates, :create) - config_script = FactoryGirl.create(:configuration_script) - template_params = { - 'name' => 'Ansible Tower', - 'type' => 'ServiceTemplateAnsibleTower', - 'prov_type' => 'generic_ansible_tower', - 'service_type' => 'atomic', - 'config_info' => { - 'template_id' => config_script.id, - } - } - - expected = { - 'results' => [a_hash_including(template_params.except('config_info'))] - } - expect do - run_post(service_templates_url, template_params) - end.to change(ServiceTemplateAnsibleTower, :count).by(1) - expect(response).to have_http_status(:ok) - expect(response.parsed_body).to include(expected) - end end end From e90405fbf9a3ba771d77248f7c5a2941968640e1 Mon Sep 17 00:00:00 2001 From: Jillian Tullo Date: Mon, 6 Feb 2017 10:17:36 -0500 Subject: [PATCH 8/9] update product feature --- config/api.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/api.yml b/config/api.yml index 085718d5055..9f7e6946499 100644 --- a/config/api.yml +++ b/config/api.yml @@ -1692,7 +1692,7 @@ - :name: delete :identifier: catalogitem_delete - :name: create - :identifier: catalogitem_new + :identifier: atomic_catalogitem_new :subcollection_actions: :post: - :name: edit From 98d8237b8844186e21bf1cb39357d847a2d7e386 Mon Sep 17 00:00:00 2001 From: Jillian Tullo Date: Mon, 13 Feb 2017 16:49:24 -0500 Subject: [PATCH 9/9] adding class_from_request_data to ServiceTempalte --- .../api/service_templates_controller.rb | 4 ++-- app/models/service_template.rb | 12 +++++++++++- spec/models/service_template_spec.rb | 14 ++++++++++++++ spec/requests/api/service_templates_spec.rb | 1 - 4 files changed, 27 insertions(+), 4 deletions(-) diff --git a/app/controllers/api/service_templates_controller.rb b/app/controllers/api/service_templates_controller.rb index 2abde4c19bf..05ffb612729 100644 --- a/app/controllers/api/service_templates_controller.rb +++ b/app/controllers/api/service_templates_controller.rb @@ -7,8 +7,8 @@ class ServiceTemplatesController < BaseController before_action :set_additional_attributes, :only => [:show] - def create_resource(type, _id, data) - catalog_item_type = data['type'].try(:constantize) || collection_class(type) + def create_resource(_type, _id, data) + catalog_item_type = ServiceTemplate.class_from_request_data(data) catalog_item_type.create_catalog_item(data.deep_symbolize_keys, @auth_user) rescue => err raise BadRequestError, "Could not create Service Template - #{err}" diff --git a/app/models/service_template.rb b/app/models/service_template.rb index aa8ac76866a..1f485689397 100644 --- a/app/models/service_template.rb +++ b/app/models/service_template.rb @@ -74,6 +74,16 @@ def self.create_catalog_item(options, auth_user) end end + def self.class_from_request_data(data) + request_type = data['prov_type'] + if request_type.include?('generic_') + generic_type = request_type.split('generic_').last + "ServiceTemplate#{generic_type.camelize}".constantize + else + ServiceTemplate + end + end + def readonly? return true if super blueprint.try(:published?) @@ -299,7 +309,7 @@ def self.default_reconfiguration_entry_point def template_valid? validate_template[:valid] end - alias_method :template_valid, :template_valid? + alias template_valid template_valid? def template_valid_error_message validate_template[:message] diff --git a/spec/models/service_template_spec.rb b/spec/models/service_template_spec.rb index 07724057ff9..7f2b3f402cb 100644 --- a/spec/models/service_template_spec.rb +++ b/spec/models/service_template_spec.rb @@ -482,6 +482,20 @@ end end + describe '.class_from_request_data' do + it 'returns the correct generic type' do + template_class = ServiceTemplate.class_from_request_data('prov_type' => 'generic_ansible_tower') + + expect(template_class).to eq(ServiceTemplateAnsibleTower) + end + + it 'returns the correct non generic type' do + template_class = ServiceTemplate.class_from_request_data('prov_type' => 'amazon') + + expect(template_class).to eq(ServiceTemplate) + end + end + describe '#create_catalog_item' do let(:user) { FactoryGirl.create(:user_with_group) } let(:ra1) { FactoryGirl.create(:resource_action, :action => 'Provision') } diff --git a/spec/requests/api/service_templates_spec.rb b/spec/requests/api/service_templates_spec.rb index bcfdf44c058..4509e6392d4 100644 --- a/spec/requests/api/service_templates_spec.rb +++ b/spec/requests/api/service_templates_spec.rb @@ -303,7 +303,6 @@ api_basic_authorize collection_action_identifier(:service_templates, :create) template = FactoryGirl.create(:orchestration_template) template_parameters = { - :type => 'ServiceTemplateOrchestration', :name => 'Orchestration Template', :service_type => 'atomic', :prov_type => 'generic_orchestration',