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 service template REST api #12594

Merged
merged 9 commits into from
Feb 15, 2017
Merged

create service template REST api #12594

merged 9 commits into from
Feb 15, 2017

Conversation

jntullo
Copy link

@jntullo jntullo commented Nov 11, 2016

create service template REST api for use in the SUI - only doing atomic initially. Will follow up with composite.

@miq-bot add_label api, enhancement, wip, euwe/no
@miq-bot assign @abellotti

config/api.yml Outdated
@@ -1386,6 +1388,8 @@
:post:
- :name: edit
:identifier: catalogitem_edit
- :name: create
:identifier: catalogitem_new
Copy link
Member

Choose a reason for hiding this comment

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

why create action at the resource level ? probably only needed on collection.

@jntullo
Copy link
Author

jntullo commented Nov 17, 2016

@bzwei @gmcculloug I worked on this based off of the atomic_req_submit here. Would appreciate some extra 👀 to ensure that I'm on the right track for creating atomic Service Templates.

Still need to add in the case for generic / composite.

@@ -4,5 +4,56 @@ class ServiceTemplatesController < BaseController
include Subcollections::Tags
include Subcollections::ResourceActions
include Subcollections::ServiceRequests

def create_resource(_type, _id, data)
Copy link
Contributor

Choose a reason for hiding this comment

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

Shouldn't the method be named create_service_template unless resource is a term used by the API

Copy link
Author

Choose a reason for hiding this comment

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

it's a term used by the API


private

def create_atomic(data)
Copy link
Contributor

Choose a reason for hiding this comment

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

This name is also misleading. Looks like here you are creating type with a workflow. Generic types are also atomic.

Copy link
Author

Choose a reason for hiding this comment

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

generic can also be created through this method. will add in a test for that.


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']
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we really want to enforce an provisioning entry point? I think now the backend have default value for all provisioning types.
In general I don't know whether it is necessary to add validation for too many fields, because even if some fields are missing at creation time, they can be added through update API.

Copy link
Author

Choose a reason for hiding this comment

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

Sounds good to me! Was just adding in the validations I saw in the other controller.

@jntullo
Copy link
Author

jntullo commented Nov 18, 2016

@miq-bot remove_label wip

@jntullo jntullo changed the title [WIP] create service template REST api create service template REST api Nov 18, 2016
@miq-bot miq-bot removed the wip label Nov 18, 2016
ra.update_attributes(:dialog => dialog, :fqname => request_data[action[:param_key]])
end
end
service_template
Copy link
Contributor

Choose a reason for hiding this comment

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

although no harm, what's the purpose of returning service_template?

def create_atomic(data)
service_template = ServiceTemplate.new(data.except('request_info'))
if data.key?('request_info')
service_template.add_resource(create_service_template_request(data['request_info']))
Copy link
Contributor

Choose a reason for hiding this comment

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

I think you need to make data['request_info'] with more structures instead of a plain hash. You code does not create service_template properly for generic_orchestration, generic_ansible_tower, and generic_container(to come). For these types different entries are added to service_resources table.

Copy link
Author

Choose a reason for hiding this comment

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

@bzwei exactly why I wanted this feedback 😃 is there a place in the code already doing this where I can follow along make it work for the REST api? Didn't quite see it in the existing controller...

{:name => 'Reconfigure', :param_key => 'reconfigure_fqname'},
{:name => 'Retirement', :param_key => 'retire_fqname'}
].each do |action|
unless request_data[action[:param_key]].nil?
Copy link
Contributor

Choose a reason for hiding this comment

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

Even if the request_data does not have an action you'll still need to create an resource_action with default entry point. Look at ServiceTemplate and its subclasses for default entry points.

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']))
Copy link
Contributor

@bzwei bzwei Nov 21, 2016

Choose a reason for hiding this comment

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

service_template.job_template = unless request_info['template_id'].nil?
ConfigurationScript.find(request_info['template_id'])
end
end
Copy link
Member

Choose a reason for hiding this comment

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

Quite a bit of code here. Any of these generic enough (base/foundation/verification) so they can live in the model at app/models/service_template.rb ?

Copy link
Author

Choose a reason for hiding this comment

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

Agreed @abellotti - once I had the logic down, was going to see about moving some of it into the model. 👍

end

def add_orchestration_template_vars(service_template, request_info)
return unless service_template.kind_of?(ServiceTemplateOrchestration)
Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Author

Choose a reason for hiding this comment

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

Yes it does, it takes in a type parameter. It's also tested in the tests (checks that the count of ServiceTemplateOrchestration increases)

end

def add_ansible_tower_job_template_vars(service_template, request_info)
return unless service_template.kind_of?(ServiceTemplateAnsibleTower)
Copy link
Contributor

Choose a reason for hiding this comment

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

same as above

request
end

def set_provision_action(service_template, dialog, request_info)
Copy link
Contributor

Choose a reason for hiding this comment

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

why do you need to separate provision from retirement and reconfigure?

Copy link
Author

Choose a reason for hiding this comment

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

Mostly separated because the provisioning entrypoint takes in the service type, the others don't, and when I put them together rubocop yelled about complexity. Will try and refactor though.


def set_retire_reconfigure_actions(config_info, dialog)
[
{:name => 'Reconfigure', :param_key => 'reconfigure_fqname', :method => 'default_reconfiguration_entry_point'},
Copy link
Contributor

Choose a reason for hiding this comment

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

You can combine two methods by adding :args => [] to the hash, so it looks like

{:name => 'Provision', :param_key..., :method ..., :args => [service_type]},
{:name => 'Reconfigure', :param_key..., :method..., :args =>[]},
...

Then self.class.send(action[:method], *action[:args])

@@ -258,6 +258,11 @@ def self.default_reconfiguration_entry_point
nil
end

def set_resource_actions(config_info, dialog)
Copy link
Contributor

Choose a reason for hiding this comment

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

I am not very convinced that we want this method in ServiceTemplate, especially here the keys in config_info are hard codes, obviously inherited from API controller. If we really want it here, can you document the use of this method?
@gmcculloug any thoughts?

Copy link
Author

Choose a reason for hiding this comment

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

Thoughts @gmcculloug?

Copy link
Member

Choose a reason for hiding this comment

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

Thanks for the reminder. Discussed with @bzwei before the break and I am not sure the best place for this code at the moment.

@abellotti Any suggestions on where this method should live.

@jntullo
Copy link
Author

jntullo commented Jan 3, 2017

ping @gmcculloug

end
save!
end

Copy link
Member

Choose a reason for hiding this comment

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

Hi @jntullo, I've looked at this with @gmcculloug a bit,
this method can stay here in app/models/service_template.rb, seems generic enough, the only thing that took a while to understand was the config_info.

Can you change the signature in this method (and update the calling sequence in API accordingly) as follows:

def set_resource_actions(ae_endpoints, dialog)

where ae_endpoints would be the following hash:

{
    :provisioning => "/.....",
    :retirement => "/.....",
    :reconfigure => "/....."
}

non-existent/nil entries honored as you have it today.

Thanks,

@abellotti
Copy link
Member

abellotti commented Jan 5, 2017

Thanks @jntullo for the update. LGTM!!

@gmcculloug @bzwei ok with you ?

@abellotti
Copy link
Member

ping @gmcculloug @bzwei

service_template = ServiceTemplate.new(data.except('config_info'))
config_info = data['config_info'].except('ae_endpoints') || {}
case service_template.type
when 'ServiceTemplateOrchestration'
Copy link
Contributor

Choose a reason for hiding this comment

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

We are going to add new type ServiceTemplateContainer, and possibly other types in the future. How do we remember to come back to update this case...when?
The API controller attempts to create a service template, but the actual ServiceTemplate has many subclasses. Need a generic way to handle subtypes.

Copy link
Author

Choose a reason for hiding this comment

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

@bzwei good question. Almost sounds like it should be a part of the initialization process

Copy link
Contributor

Choose a reason for hiding this comment

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

@jntullo I am currently developing logic to create service template for the new AnsiblePlaybook service type. I think for each type we should have a class method to create a full stack of catalog item definition, meaning ServiceTemplate, ServiceResource, and ResourceActions. We can work together to move us to the same direction.

Copy link
Author

Choose a reason for hiding this comment

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

that sounds good to me @bzwei

Copy link
Author

Choose a reason for hiding this comment

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

@bzwei are you okay with this PR as is, and then enhancing it as the other work gets completed?

Copy link
Contributor

Choose a reason for hiding this comment

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

@jntullo We can merge this one if it becomes a blocker for other development work; otherwise I would think this is the right place to refactor.

I started to add the following to service_template.rb

  # create ServiceTemplate and supporting ServiceResources and ResourceActions
  def self.create_catalog_item(options)

  end

The implementation is empty right now. You can move logic to this block and corresponding subclasses.
The method should return an instance of ServiceTemplate or subclass.

The method name can be debatable; I could not think of a better name.

Copy link
Member

Choose a reason for hiding this comment

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

any update on this PR ?

Copy link
Author

@jntullo jntullo Jan 24, 2017

Choose a reason for hiding this comment

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

@abellotti I will be updating this soon (this week) based off of the class methods @bzwei and I have been working on


def create_resource(_type, _id, data)
raise 'Must provide a type' unless data['type']
type = data['type'].constantize
Copy link
Contributor

Choose a reason for hiding this comment

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

Here we have both _type and type. Consider to rename either one to something more meaningful.

:instance_type => [flavor.id, flavor.name],
:src_ems_id => [ems.id, ems.name],
:provision => {
:fqname => ra1.fqname,
Copy link
Contributor

Choose a reason for hiding this comment

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

In reality you still need a service dialog for provisioning.

@bzwei
Copy link
Contributor

bzwei commented Feb 2, 2017

LGTM except for the two minor comments.

@miq-bot miq-bot added wip and removed wip labels Feb 6, 2017
@imtayadeway
Copy link
Contributor

Please rebase this PR to incorporate #13846 in order to ensure api.yml integrity ❤️ ❤️ ❤️

@jntullo
Copy link
Author

jntullo commented Feb 13, 2017

ping @abellotti

@@ -4,5 +4,12 @@ class ServiceTemplatesController < BaseController
include Subcollections::Tags
include Subcollections::ResourceActions
include Subcollections::ServiceRequests

def create_resource(type, _id, data)
catalog_item_type = data['type'].try(:constantize) || collection_class(type)
Copy link
Member

Choose a reason for hiding this comment

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

we really shouldn't be running constantize on user specified data. Prefer if model returned the class for us. Are these known classes, or subclasses of known base classes ?

see app/models/miq_request's class_from_request_data() that we call from API. Might need something similar here.

"openstack" => :ServiceTemplate,
"redhat" => :ServiceTemplate,
"vmware" => :ServiceTemplate
}.freeze
Copy link
Author

Choose a reason for hiding this comment

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

@bzwei Can you verify that these are accurate?

Copy link
Member

Choose a reason for hiding this comment

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

wouldn't logic like the following work here ?

return klass if request_type == ServiceTemplate.name

request_klass = ServiceTemplate.descendants.detect { |sub_klass| request_type == sub_klass.name }
return request_klass if request_klass

since:

ServiceTemplate.descendants.collect(&:name)
=> ["ServiceTemplateAnsibleTower", "ServiceTemplateGeneric", "ServiceTemplateOrchestration", "ServiceTemplateAnsiblePlaybook"]

@miq-bot
Copy link
Member

miq-bot commented Feb 13, 2017

This pull request is not mergeable. Please rebase and repush.

@@ -75,6 +88,12 @@ def self.create_catalog_item(options, auth_user)
end
end

def self.class_from_request_data(data)
request_type = data['prov_type']
model_symbol = PROV_TYPE_TO_MODEL[request_type] || raise(ArgumentError, 'Invalid prov_type')
Copy link
Contributor

Choose a reason for hiding this comment

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

We can/should avoid lookup from a hash and prefer naming convention. If the type starts with generic_, we can do an auto conversion from generic_abc to ServiceTemplateAbc; for all the rest, directly ServiceTemplate.

Copy link
Author

Choose a reason for hiding this comment

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

sounds good, thanks @bzwei

@miq-bot
Copy link
Member

miq-bot commented Feb 13, 2017

This pull request is not mergeable. Please rebase and repush.

@miq-bot
Copy link
Member

miq-bot commented Feb 14, 2017

Checked commits jntullo/manageiq@ec70f64~...98d8237 with ruby 2.2.6, rubocop 0.47.1, and haml-lint 0.20.0
4 files checked, 0 offenses detected
Everything looks good. 🏆

request_type = data['prov_type']
if request_type.include?('generic_')
generic_type = request_type.split('generic_').last
"ServiceTemplate#{generic_type.camelize}".constantize
Copy link
Member

Choose a reason for hiding this comment

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

/cc @bzwei @gmcculloug you guys ok with this constantize ?

Copy link
Contributor

Choose a reason for hiding this comment

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

👍

@abellotti
Copy link
Member

Thanks @bzwei and @jntullo for this API Enhancement. LGTM!!

@abellotti abellotti merged commit 8e256a6 into ManageIQ:master Feb 15, 2017
@abellotti abellotti added this to the Sprint 55 Ending Feb 27, 2017 milestone Feb 15, 2017
@bzwei
Copy link
Contributor

bzwei commented Feb 15, 2017

@jntullo Good job!

@chrisarcand chrisarcand deleted the create_catalog_item branch November 28, 2017 19:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants