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

Added ability to include recipes smartly #1765

Open
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

sjamgade
Copy link

@sjamgade sjamgade commented Jan 9, 2019

Roles can get smart by making recipe inform the role about its dependencies
across the proposal.

So a recipe will be included by the role only if the attributes
registered by the recipe are actually changed in current chef-client
run.

a recipe can register its dependencies as

mydependson = {
    "horizon::server" => [
        ['glance','api','bind_port']
    ]
}

BarclampLibrary::Barclamp::DependsOn.add(mydependson)

So here the "horizon::server" is informing about its
dependencies.

This is accomplished by comparing the value of the current attributes of node
against the one already stored in the databag after the previous successful
chef-client run.

BarclampLibrary::Barclamp::DependsOn.add:
takes in a map:

     recipe_name => [
                [barclampname,drill,down,till,value],
                [otherbarclampname,onlyhere],
               ]

This map is flushed and recreated only everyrun, however like resources
this could also be cached.

this behaviour can be altered by adding flag to config and using it
'include_recipe_smartly', however that is an extension to this behavior
and can be addressed in subsequent commits

chef/cookbooks/barclamp/libraries/smartroles.rb Outdated Show resolved Hide resolved
chef/cookbooks/barclamp/libraries/smartroles.rb Outdated Show resolved Hide resolved
chef/cookbooks/barclamp/libraries/smartroles.rb Outdated Show resolved Hide resolved
chef/cookbooks/barclamp/libraries/smartroles.rb Outdated Show resolved Hide resolved
chef/cookbooks/barclamp/libraries/barclamp_library.rb Outdated Show resolved Hide resolved
chef/cookbooks/barclamp/libraries/barclamp_library.rb Outdated Show resolved Hide resolved
chef/cookbooks/barclamp/libraries/barclamp_library.rb Outdated Show resolved Hide resolved
chef/cookbooks/barclamp/libraries/barclamp_library.rb Outdated Show resolved Hide resolved
chef/cookbooks/barclamp/libraries/barclamp_library.rb Outdated Show resolved Hide resolved
chef/cookbooks/barclamp/libraries/barclamp_library.rb Outdated Show resolved Hide resolved
chef/cookbooks/barclamp/libraries/barclamp_library.rb Outdated Show resolved Hide resolved
@recipe_depedancies ||= Mash.new
@recipe_depedancies.merge!(dependency)
end
def get(recipe)
Copy link

Choose a reason for hiding this comment

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

Layout/EmptyLineBetweenDefs: Use empty lines between method definitions. (https://github.com/bbatsov/ruby-style-guide#empty-lines-between-methods)

chef/cookbooks/barclamp/libraries/barclamp_library.rb Outdated Show resolved Hide resolved
Copy link
Contributor

@nicolasbock nicolasbock left a comment

Choose a reason for hiding this comment

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

Can you think of a way to test this feature? Replacing include_recipe would introduce a potentially large regression 😄

Can you give some ballpark as to what performance impact this would have? Or maybe do you have examples where you measured the performance impact?

chef/cookbooks/barclamp/libraries/barclamp_library.rb Outdated Show resolved Hide resolved
@sjamgade
Copy link
Author

sjamgade commented Jan 9, 2019

i did do some initial testing, here is the sample: sjamgade/crowbar-openstack@4fc8662

this is not going to replace include_recipe. but add alternate way to conditionally execute recipes. These both can coexist

@nicolasbock
Copy link
Contributor

What I was trying to get at was that by replacing an include_recipe with an include_recipe_smartly in a recipe you are expecting a performance gain because the dependency is not included.

Besides testing whether a dependency is really not included when its attributes have not changed, we need to ask the following question:

For maximizing performance gain, the list of attributes of a dependency needs to be minimal in a sense that it contains only necessary attributes. However, a missing attribute introduces a regression.

How do we test that this set does not introduce a regression because it's missing something?

@sjamgade
Copy link
Author

sjamgade commented Jan 9, 2019

thats one way to look at it.
I see it other way: where recipes (and the resources) are grouped based on attributes.

one shortcut I though of
eg:

mydependson = {
    "horizon::server" => [
        ["horizon"]       # depend on the entire proposal
        ['glance','api','bind_port']    # this is across barclamps, 
    ]
}

I do no expect to see too many across barclamp dependencies and if there are, these recipes (and/or resources ) can be grouped a separate recipe

chef/cookbooks/barclamp/libraries/barclamp_library.rb Outdated Show resolved Hide resolved
chef/cookbooks/barclamp/libraries/barclamp_library.rb Outdated Show resolved Hide resolved
chef/cookbooks/barclamp/libraries/barclamp_library.rb Outdated Show resolved Hide resolved
chef/cookbooks/barclamp/libraries/barclamp_library.rb Outdated Show resolved Hide resolved
prev_cfg = load(group, barclamp, instance)
instance = infer_instance(group, barclamp, instance)
cur_cfg = Chef::Role.load "#{barclamp}-config-#{instance}"
return prev_cfg, cur_cfg.default_attributes[barclamp]
Copy link

Choose a reason for hiding this comment

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

Style/RedundantReturn: Redundant return detected. To return multiple values, use an array. (https://github.com/bbatsov/ruby-style-guide#no-explicit-return)

@sjamgade sjamgade requested review from vuntz and rhafer January 22, 2019 15:12
Chef::Log.debug("[smart] due to change in: #{dependency}")
include_recipe recipe
return
end # each
Copy link

Choose a reason for hiding this comment

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

Style/CommentedKeyword: Do not place comments on the same line as the end keyword.

chef/cookbooks/barclamp/libraries/smartroles.rb Outdated Show resolved Hide resolved
@sjamgade sjamgade force-pushed the smartrecipes branch 2 times, most recently from e9f2fa9 to e8a34bf Compare January 22, 2019 15:40
return hash
end
end
Chef::Log.debug("[smart] hash does not respond to []")
Copy link
Member

Choose a reason for hiding this comment

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

this (and the variable name) is misleading. if variable doesn't respond to [] it's most probably not a hash.

Copy link
Author

Choose a reason for hiding this comment

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

how about collection?
since it is a collection of values

Copy link
Member

@skazi0 skazi0 Jan 24, 2019

Choose a reason for hiding this comment

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

it could also be a single value. this is exactly when the log would trigger. I generally think that this message could be more informative (e.g. "%param name% is not a collection" or sth like this).

Copy link
Author

Choose a reason for hiding this comment

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

exactly, it could be a single value or a collection ( i could not think of a word for that)
and that is why the log says the collection did not respond to ... because it is a int/string/....

Roles can get smart by making recipe inform the role about its dependencies
across the proposal.

So a recipe will be included by the role only if the attributes
registered by the recipe are actually changed in current chef-client
run.

a recipe can register its dependencies as
```
mydependson = {
    "horizon::server" => [
        ['glance','api','bind_port']
    ]
}

BarclampLibrary::Barclamp::DependsOn.add(mydependson)
```
So here the "horizon::server"  is informing about its
dependencies.

This is accomplished by comparing the value of the current attributes of node
against the one already stored in the databag after the previous successful
chef-client run.

BarclampLibrary::Barclamp::DependsOn.add:
takes in a map:

     recipe_name => [
                [barclampname,drill,down,till,value],
                [otherbarclampname,onlyhere],
               ]

This map is flushed and recreated only everyrun, however like resources
this could also be cached.

this behaviour can be altered by adding flag to config and using it
'include_recipe_smartly', however that is an extension to this behavior
and can be addressed in subsequent commits

Use the role(proposal) that was committed to compare against databag

This object enables us to do real comparison on proposal level.

Thus every barclamp can have proposal level dependency

hound fixes and refactor
collection = collection[item]
end
rescue NoMethodError
Chef::Log.info("[smart] collection did not respond to []/key? while looking for #{item}")
Copy link

Choose a reason for hiding this comment

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

Metrics/LineLength: Line is too long. [101/100] (https://github.com/SUSE/style-guides/blob/master/Ruby.md#metricslinelength)

collection = collection[item]
end
rescue NoMethodError
Chef::Log.info("[smart] collection did not respond to []/key? while looking for #{item}")
Copy link

Choose a reason for hiding this comment

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

Metrics/LineLength: Line is too long. [101/100] (https://github.com/SUSE/style-guides/blob/master/Ruby.md#metricslinelength)

@sjamgade
Copy link
Author

Modified the example test to include proposal level depedancy : sjamgade/crowbar-openstack@954354e

@vuntz
Copy link
Member

vuntz commented Jan 24, 2019

One key thing to be aware of is that not all barclamps save something to the data bags, and among the ones that do, not all of them simply copy the content of the proposal (eg: dns and ntp).

So the proposed approach here only works for the openstack barclamps. Which is still not too bad, but still something to be aware of.

@sjamgade
Copy link
Author

I didnot know that, thanks @vuntz
Non-openstack barclamps are relatively fast and I dont think we need to consider them with respect to this PR.

any more reviews ? @skazi0

rhafer
rhafer previously requested changes Jan 31, 2019
Copy link
Contributor

@rhafer rhafer left a comment

Choose a reason for hiding this comment

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

Non-openstack barclamps are relatively fast and I dont think we need to consider them with respect to this PR.

But I guess it means that if e.g. something (anything) changes in a barclamp that doesn't maintain the config databags in the way we expect it, we need to always run the full set of recipes, right? As far as I can see the current code doesn't really handle that case. Also keep in mind that even not all barclamps from crowbar-openstack are writing the config databags yet. At least database is not using it currently I think.

I haven't fully made up my mind on this change yet. On the one hand it introduces a bit of magic that can have weird side-effects (e.g. a local chef-client run will not revert up any manual config change that were done or crowbar_join might be missing stuff on boot up). And there is a risk of this change causing some "interesting" corner cases. OTOH, I guess this will reduce the time needed for a deployment and may be more importantly for later config changes so there is definitely a significant benefit.

In general I think the code could benefit from a few more comments here and there to make it easier for reviewers to understand what it actually does.

mydependson = {
    "horizon::server" => [
        ["horizon"]       # depend on the entire proposal

Does this part really work with the current code?

BTW, I didn't really check this, but are you sure that this PR also works when e.g. the chef-client run on a node fails during apply. (Is the databag only written after a successful run?)

@sjamgade
Copy link
Author

Non-openstack barclamps are relatively fast and I dont think we need to consider them with respect to this PR.

But I guess it means that if e.g. something (anything) changes in a barclamp that doesn't maintain the config databags in the way we expect it, we need to always run the full set of recipes, right? As far as I can see the current code doesn't really handle that case. Also keep in mind that even not all barclamps from crowbar-openstack are writing the config databags yet. At least database is not using it currently I think.

you are right, the code does not handle this case (recipes dependent on stuff outside the databags), but such recipes can continue to use include_recipe and only those which rely entirely on databag can use include_recipe_smartly
This PR just adds support for something like this and does not alter the current behavior.

I haven't fully made up my mind on this change yet. On the one hand it introduces a bit of magic that can have weird side-effects (e.g. a local chef-client run will not revert up any manual config change that were done or crowbar_join might be missing stuff on boot up). <snip>

local chef run can be asked to always run via a "config" option, which I will add if this PR sees the master branch.

In general I think the code could benefit from a few more comments here and there to make it easier for reviewers to understand what it actually does.
Will do. Thanks.

mydependson = {
    "horizon::server" => [
        ["horizon"]       # depend on the entire proposal

Does this part really work with the current code?

yes I have been testing it. :-)

BTW, I didn't really check this, but are you sure that this PR also works when e.g. the chef-client run on a node fails during apply. (Is the databag only written after a successful run?)

yes :-)

@sjamgade
Copy link
Author

sjamgade commented Feb 8, 2019

I understand there is a lot of uncertainty about the behavior of almost all barclamps as everyone here is suggesting.

But this PR is merely adding support for skipping unnecessary stuff and not actually affecting the real workflow. And as followup PR, I will add the option to disable skipping entirely.

Is there anything else we can do to move things forward here.

@rhafer
Copy link
Contributor

rhafer commented Feb 8, 2019

I understand there is a lot of uncertainty about the behavior of almost all barclamps as everyone here is suggesting.

There is also some uncertainty (at least from me) about how much additional maintenance this requires. This finally adds another place where we carefully need to think and maintain dependencies between barclamps (at least of the barclamps for which we actually want to use it).

We haven't been particularly good at that in the past IMO :)
You're obviously right, though that this PR itself doesn't change any behavior for now until we start adapting the role_recipes. Maybe it's fine to just merge it and continue with experitentally enabling some roles for this.

Is there anything else we can do to move things forward here.

I guess some more reviews/opinions form others might help. @aspiers maybe?

@rhafer rhafer requested a review from aspiers February 8, 2019 14:27
@rhafer rhafer dismissed their stale review February 8, 2019 14:27

most concerns I raised have been addressed

include_recipe recipe
included = true
break
end.empty? and begin
Copy link

Choose a reason for hiding this comment

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

Style/AndOr: Use && instead of and. (https://github.com/bbatsov/ruby-style-guide#no-and-or-or)

# "horizon::server" => [
# ['glance','api','bind_port'],
# ['horizon']
# ]
Copy link

Choose a reason for hiding this comment

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

Layout/Tab: Tab detected. (https://github.com/bbatsov/ruby-style-guide#spaces-indentation)
Layout/CommentIndentation: Incorrect indentation detected (column 2 instead of 8).

# if it has changed include the recipe
# "horizon::server" => [
# ['glance','api','bind_port'],
# ['horizon']
Copy link

Choose a reason for hiding this comment

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

# Iterate over the dependcies and find if either have changed
# if it has changed include the recipe
# "horizon::server" => [
# ['glance','api','bind_port'],
Copy link

Choose a reason for hiding this comment

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

def include_recipe_smartly(*list_of_recipes)
# Iterate over the dependcies and find if either have changed
# if it has changed include the recipe
# "horizon::server" => [
Copy link

Choose a reason for hiding this comment

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

end

# a attrlist is ['api','bind_port'] (array)
# and collection is assumed to be a hash like object
Copy link

Choose a reason for hiding this comment

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

return prev_cfg, cur_cfg.default_attributes[barclamp]
end

# a attrlist is ['api','bind_port'] (array)
Copy link

Choose a reason for hiding this comment

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

# this method will return collection['api']['bind_port']
#
# - abort and return nil , if any key not found
# - log accordingly
Copy link

Choose a reason for hiding this comment

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

Layout/Tab: Tab detected. (https://github.com/bbatsov/ruby-style-guide#spaces-indentation)
Layout/CommentIndentation: Incorrect indentation detected (column 2 instead of 8).

# and collection is assumed to be a hash like object
# this method will return collection['api']['bind_port']
#
# - abort and return nil , if any key not found
Copy link

Choose a reason for hiding this comment

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

# a attrlist is ['api','bind_port'] (array)
# and collection is assumed to be a hash like object
# this method will return collection['api']['bind_port']
#
Copy link

Choose a reason for hiding this comment

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

Layout/Tab: Tab detected. (https://github.com/bbatsov/ruby-style-guide#spaces-indentation)
Layout/TrailingWhitespace: Trailing whitespace detected. (https://github.com/bbatsov/ruby-style-guide#no-trailing-whitespace)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
6 participants