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

Introduce bundler groups #15459

Merged
merged 12 commits into from Jul 5, 2017

Conversation

jrafanie
Copy link
Member

@jrafanie jrafanie commented Jun 27, 2017

What is a bundler group?

  • gem label/tag to group related dependencies
  • can tag with multiple bundler groups:
gem "good_migrations", :group => :development, :test

OR:

group :development, :test do
  gem "good_migrations"
  gem "parallel_tests"
  gem "rspec-rails",      "~>3.6.0"
end

Gem with no group are put in the :default bundler group.

How are they used?

  • the default config/application.rb does:

Bundler.require(*Rails.groups)

What this does:

  • groups get expanded to:
    default bundler group + the Rails.env group: (test, development, or production)
  • populates the load path and optionally requires the different groups

Why?

We need to isolate dependencies but splitting them all out is hard as some are shared dependencies and the "core" manageiq features (models, basic libs) is not in a gemspec file that each worker could include in a Gemfile.

First step solution, use bundler groups to group dependencies and requires

The idea: "tag" our gems with a "specific feature" AND a manageiq_default group:

group :smartstate, :manageiq_default do
  gem "manageiq-smartstate",            "~>0.1.1",       :require => false
end
  • the smartstate feature is enabled by requiring either the smartstate or manageiq_default group.

How?

config/application.rb can now do:

ENV['BUNDLER_GROUPS'] ||= "manageiq_default"

if defined?(Bundler)
  groups = ENV['BUNDLER_GROUPS'].split(",").collect(&:to_sym)
  ...
  Bundler.require(*Rails.groups, *groups)
end

So, you can inject your dependencies at command line and for each process type by injecting them into an environment variable.

Workers specify their dependencies OR you can do BUNDLER_GROUPS="rest_api" bin/rails server

Is this the long term solution?

No. The proper way to do this is to move all dependencies where they belong and have a Gemfile for each process type. But, that's HARD and risky.

Bundler groups are safer and can be done simultaneously for 4.6 gaprindashvili and a bit more aggressively for master/5.0 h-release. EDIT @Fryguy

Summary:

  • memory and boot improvements now
  • organization and feature "seams"
  • eventual extraction of dependencies to code providing that feature (less disk space)

Loading all dependencies via the manageiq_default bundle group:

2311 requires
119 MB
real	0m5.918s
user	0m4.268s
sys	0m1.602s

With none of the manageiq_default gems:

1949 requires
86 MB

real	0m4.279s
user	0m2.866s
sys	0m1.358s

Not all workers will exclude all of the manageiq_default gems but it can pick and choose which ones it needs.

@jrafanie jrafanie changed the title [WIP] Introduce bundler groups Introduce bundler groups Jun 27, 2017
Copy link
Member

@NickLaMuro NickLaMuro left a comment

Choose a reason for hiding this comment

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

Looks good, just a few minor things I noticed since seeing this last. So I guess this whole effort only took you like... say... a couple hours to put together, right? :trollface:

Bundler.require(*Rails.groups, :ui_dependencies)
groups = ENV['BUNDLER_GROUPS'].split(",").collect(&:to_sym)
puts "loading: #{Rails.groups.inspect}"
puts "loading: #{groups.inspect}"
Copy link
Member

Choose a reason for hiding this comment

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

You should remove these.

Copy link
Member Author

Choose a reason for hiding this comment

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

I like them. Maybe I can disable them by default and put a bundler group nerd flag to enable them.

Copy link
Member

Choose a reason for hiding this comment

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

I support the concept of "Nerd Flags" with this. Seems like only a few people would actually be interested in this info (I would probably fall in that category at some point :neckbeard: ).

@@ -17,6 +17,7 @@ def do_before_work_loop

module ClassMethods
def start_worker(*args)
$log.info("#{self.name} $LOADED_FEATURES: #{$LOADED_FEATURES.length}")
Copy link
Member

Choose a reason for hiding this comment

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

Do we want to keep this?

Copy link
Member Author

Choose a reason for hiding this comment

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

More nerd flags here. I guess it can go

@@ -20,6 +20,7 @@ class TemporaryFailure < RuntimeError
SAFE_SLEEP_SECONDS = 60

def self.start_worker(*args)
$log.info("#{self.name} $LOADED_FEATURES: #{$LOADED_FEATURES.length}")
Copy link
Member

Choose a reason for hiding this comment

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

Do we want to keep this?

Copy link
Member Author

Choose a reason for hiding this comment

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

Same as above... 🤓

@@ -370,7 +370,7 @@ def self.runner_script
end

def start_runner_via_spawn
pid = Kernel.spawn(self.class.build_command_line(guid), :out => "/dev/null", :err => [Rails.root.join("log", "evm.log"), "a"])
pid = Kernel.spawn(self.class.build_command_line(guid), [:out, :err] => [Rails.root.join("log", "evm.log"), "a"])
Copy link
Member

Choose a reason for hiding this comment

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

Huh... was this always like this? Oh well, 👍

Copy link
Member Author

Choose a reason for hiding this comment

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

It was until I tried to puts above ... 😉

"MiqVimBrokerWorker" => [:manageiq_default],
"MiqWebServiceWorker" => %i(rest_api ui_dependencies web_server),
"MiqWebsocketWorker" => %i(ui_dependencies web_server web_socket)
}.freeze
Copy link
Member

Choose a reason for hiding this comment

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

Ruh-roh... someone went "full @bdunne " on sortingthat array... :trollface:

On a slightly more serious note, should we just always use %i() for these, just to be consistent?

Copy link
Member Author

Choose a reason for hiding this comment

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

haha, yeah... In order to mass rename some of the "conservative by default: manageiq_default" to more aggressive values... I had to sort workers by name, sort groups by name... Clearly, @bdunne would be proud.

Yeah, I can make even the single element arrays %i if that's helpful.

Copy link
Member

Choose a reason for hiding this comment

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

Yeah, I can make even the single element arrays %i if that's helpful.

Whatever you think makes sense. I think swapping between the two makes it a bit noisy to look at, but it is a pedantic concern. It's not like I am @bdunne ... #sickBurn #atTenThirtyAtNight #goToBedNick

Copy link
Member

Choose a reason for hiding this comment

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

👍 I approve of the sorting and agree with being consistent. (Although I prefer [] over %i())

Copy link
Member Author

Choose a reason for hiding this comment

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

Ok, I made the decision to not make changes... We can always change this if there are compelling reasons later. I like the lack of commas and feel that %i(...) is not required for a single element arrays.

I'm on the fence on this so do speak up if your OCD radar wants it one way or the other.

Copy link
Member

Choose a reason for hiding this comment

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

@jrafanie Well, at least fix your rubocop errors for the values being mis-aligned :trollface:

@jrafanie
Copy link
Member Author

@NickLaMuro did you like my 📖 in the PR description? 😉

@NickLaMuro
Copy link
Member

@NickLaMuro did you like my 📖 in the PR description? 😉

It was a very cute "short story" you put together (though, I feel like I have seen the contents in some kind of "markdown-based-vim-like-presentation" or something...). I eagerly await your future releases/novels/epics.

Gemfile Outdated
gem "responders", "~>2.0"
gem "ruby-dbus" # For external auth
gem "thin", "~>1.7.0", :require => false
gem "secure_headers", "~>3.0.0"
Copy link
Member

Choose a reason for hiding this comment

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

This should be above thin :trollface:


### shared dependencies
group :google, :openshift, :manageiq_default do
gem "sshkey", "~>1.8.0", :require => false
Copy link
Member

Choose a reason for hiding this comment

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

Should this be moved to the provider gems instead? Does it need to be listed in the :google group here since I only see app/models/container_deployment.rb:239: require 'sshkey'

Copy link
Member Author

Choose a reason for hiding this comment

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

Yes, for reference, the goal was to create groups and move dependencies into the groups to make it obvious where things belong. The next step is to push these dependencies to the correct place. Putting these in groups makes this very obvious.

end

group :automate, :cockpit, :manageiq_default do
gem "open4", "~>1.3.0", :require => false
Copy link
Member

Choose a reason for hiding this comment

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

Same concern here

"MiqVimBrokerWorker" => [:manageiq_default],
"MiqWebServiceWorker" => %i(rest_api ui_dependencies web_server),
"MiqWebsocketWorker" => %i(ui_dependencies web_server web_socket)
}.freeze
Copy link
Member

Choose a reason for hiding this comment

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

👍 I approve of the sorting and agree with being consistent. (Although I prefer [] over %i())

jrafanie and others added 8 commits June 28, 2017 11:43
Use an environment variable for now to default which groups are loaded.
In workers, override this variable with what individual workers need.

Delay requires or move them to where they are needed.
Please enter the commit message for your changes. Lines starting
Defines a variable for defining the default bundler groups in the
lib/workers/miq_worker_types.rb file that is reusable.
Converts MIQ_WORKER_TYPES into a hash to then be usable to set the
bundler groups for a particular worker.

Also allows the run_single_worker script function with the changes to
the MiqWorker and MiqWorker::Runner classes to work with the new worker
spawning workflow.

Using this method removes duplicate code in the lib/workers/bin
directory and makes things simpler to extend in the future.
This manageiq_default group functions like the :default group that all ungrouped
gems are put in.  We require this new group for all processes by default
and specifici worker processes can bypass it via the BUNDLER_GROUPS env
variable or updating the list of groups in the worker class list.
@jrafanie jrafanie force-pushed the bundler_groups_with_runner_script branch from 1904ad0 to 2c295b4 Compare June 28, 2017 15:52
gem "simple-rss", "~>1.3.1", :require => false
gem "snmp", "~>1.2.0", :require => false
gem "sshkey", "~>1.8.0", :require => false
gem "thin", "~>1.7.0", :require => false
Copy link
Member Author

Choose a reason for hiding this comment

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

Very important: thin is completely gone since it doesn't support our async notifications using websockets.

@jrafanie jrafanie force-pushed the bundler_groups_with_runner_script branch from 2c295b4 to fa71aa5 Compare June 28, 2017 16:02
@jrafanie
Copy link
Member Author

Ok, I think I addressed all the things. Throw more 🍅 .

Is everyone ok with being aggressive in minimizing dependencies in workers and fixing any bugs that come out from that? We can always change specific workers to [:manageiq_default] if we need to.

@chrisarcand
Copy link
Member

Looks good!

Is everyone ok with being aggressive in minimizing dependencies in workers and fixing any bugs that come out from that?

👍

I'm guessing from the split you do that multiple groups can be set on the command line by comma-delimiting them?

@NickLaMuro
Copy link
Member

Is everyone ok with being aggressive in minimizing dependencies in workers and fixing any bugs that come out from that? We can always change specific workers to [:manageiq_default] if we need to.

Yes, because by default, we will be using the default anyway, so we will only see this when using spawn.

@jrafanie
Copy link
Member Author

I'm guessing from the split you do that multiple groups can be set on the command line by comma-delimiting them?

Yes, BUNDLER_GROUPS="ui_dependencies,seed" MIQ_SPAWN_WORKERS=true be rake evm:start

@jrafanie
Copy link
Member Author

Ok, @bdunne I think all the feedback has been addressed. I pushed a minor fix I found while doing the follow up to this PR... The fix ensures we check for the ui classic engine constant since that is what would setup the autoload paths.

@miq-bot
Copy link
Member

miq-bot commented Jun 29, 2017

Some comments on commits jrafanie/manageiq@fdc3b4c~...dc40ab2

config/application.rb

  • ⚠️ - 27 - Detected puts. Remove all debugging statements.
  • ⚠️ - 28 - Detected puts. Remove all debugging statements.

lib/workers/bin/run_single_worker.rb

  • ⚠️ - 41 - Detected puts. Remove all debugging statements.

@miq-bot
Copy link
Member

miq-bot commented Jun 29, 2017

Checked commits jrafanie/manageiq@fdc3b4c~...dc40ab2 with ruby 2.2.6, rubocop 0.47.1, and haml-lint 0.20.0
11 files checked, 2 offenses detected

config/application.rb

  • ❗ - Line 27, Col 5 - Rails/Output - Do not write to stdout. Use Rails's logger if you want to log.
  • ❗ - Line 28, Col 5 - Rails/Output - Do not write to stdout. Use Rails's logger if you want to log.

manageiq_plugin "manageiq-providers-amazon"
end

group :ansible, :manageiq_default do
Copy link
Member

@Fryguy Fryguy Jun 30, 2017

Choose a reason for hiding this comment

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

Minor, but does this group mean ansible_tower, embedded_ansible or both? If the latter, why not just have two group names. Having it as ansible-only feels confusing

group, :ansible_tower, :embedded_ansible, :manageiq_default do

Copy link
Member Author

Choose a reason for hiding this comment

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

From a high level, it's an ansible feature, to talk to ansible. Most of the providers give a way to talk to the provider and this is the same. So, it's both.


group :foreman, :manageiq_default do
manageiq_plugin "manageiq-providers-foreman"
gem "foreman_api_client", ">=0.1.0", :require => false, :git => "https://github.com/ManageIQ/foreman_api_client.git", :branch => "master"
Copy link
Member

Choose a reason for hiding this comment

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

Don't we own this gem? We should just cut a release.

Copy link
Member

Choose a reason for hiding this comment

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

On this note, we used to have the savon gem somewhere for automate model purposes. However, I can't find it anymore. If we still need to provide it I think that it should now live in either manageiq-content's gemspec, or even better in manageiq-appliance's gem listing.

Copy link
Member Author

Choose a reason for hiding this comment

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

Sure... that's outside the scope of this PR. I'm not sure why we're riding master here. Lots of the "bundler groups" have these developer friendly things that hopefully are easier to see with bundler groups. We can fix them one by one.

Copy link
Member

Choose a reason for hiding this comment

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

Agreed that it's outside the PR scope...just wanted to get the comment out there :)

gem "sshkey", "~>1.8.0", :require => false
end

group :automate, :cockpit, :manageiq_default do
Copy link
Member

Choose a reason for hiding this comment

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

Does automate here mean automation_engine or the automate model (i.e. manageiq-content)?

Copy link
Member Author

Choose a reason for hiding this comment

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

We can rename to automate_engine if you want... I tried to line these up with basic features than to try to be very specific.

Copy link
Member

@Fryguy Fryguy Jun 30, 2017

Choose a reason for hiding this comment

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

I tried to line these up with basic features than to try to be very specific.

I'm concerned that this will actually make things more confusing (as exemplified here and with the ansible comment above) (NOTE: not pushing my agenda...feel free to say "maybe later" 😉 )


### end of provider bundler groups

group :automate, :seed, :manageiq_default do
Copy link
Member

Choose a reason for hiding this comment

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

I think the :automate group should be named :automation_engine to distinguish it from the automate model.

Copy link
Member Author

Choose a reason for hiding this comment

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

Ok

@@ -24,9 +24,6 @@ def preload_for_console
end

def preload_for_worker_role
# Make these constants globally available
::UiConstants

Copy link
Member

Choose a reason for hiding this comment

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

😍

Copy link
Member

Choose a reason for hiding this comment

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

@jrafanie looks like this is still needed.....

17:04:06 bdunne@bdunne-t460p:~/projects/redhat/manageiq (unclaimed_deletions)$ rails s
=> Booting Puma
=> Rails 5.0.4 application starting in development on http://localhost:3000
=> Run `rails server -h` for more startup options
** ManageIQ master; Database: adapter=postgresql, name=vmdb_development, host=
** Using session_store: ActionDispatch::Session::MemCacheStore
I, [2017-07-05T17:07:40.017774 #3074]  INFO -- : Initializing websocket worker!
Puma starting in single mode...
* Version 3.3.0 (ruby 2.3.3-p222), codename: Jovial Platypus
* Min threads: 5, max threads: 5
* Environment: development
* Listening on tcp://localhost:3000
Use Ctrl-C to stop
W, [2017-07-05T17:07:49.288776 #3074]  WARN -- : DEPRECATION WARNING: You didn't set `secret_key_base`. Read the upgrade documentation to learn more about this new config option. (called from env_config at /home/bdunne/.gem/ruby/2.3.3/gems/railties-5.0.4/lib/rails/application.rb:246)
I, [2017-07-05T17:07:49.289844 #3074]  INFO -- : Started GET "/" for ::1 at 2017-07-05 17:07:49 -0400
I, [2017-07-05T17:07:51.393252 #3074]  INFO -- : Processing by DashboardController#login as HTML
F, [2017-07-05T17:07:51.649379 #3074] FATAL -- : Error caught: [NameError] uninitialized constant ApplicationController::PPCHOICES
/home/bdunne/.gem/ruby/2.3.3/bundler/gems/manageiq-ui-classic-937e70b2d07b/app/controllers/application_controller.rb:1874:in `get_global_session_data'
/home/bdunne/.gem/ruby/2.3.3/gems/activesupport-5.0.4/lib/active_support/callbacks.rb:382:in `block in make_lambda'
/home/bdunne/.gem/ruby/2.3.3/gems/activesupport-5.0.4/lib/active_support/callbacks.rb:150:in `block (2 levels) in halting_and_conditional'
/home/bdunne/.gem/ruby/2.3.3/gems/actionpack-5.0.4/lib/abstract_controller/callbacks.rb:12:in `block (2 levels) in <module:Callbacks>'
/home/bdunne/.gem/ruby/2.3.3/gems/activesupport-5.0.4/lib/active_support/callbacks.rb:151:in `block in halting_and_conditional'
/home/bdunne/.gem/ruby/2.3.3/gems/activesupport-5.0.4/lib/active_support/callbacks.rb:454:in `block in call'
/home/bdunne/.gem/ruby/2.3.3/gems/activesupport-5.0.4/lib/active_support/callbacks.rb:454:in `each'
/home/bdunne/.gem/ruby/2.3.3/gems/activesupport-5.0.4/lib/active_support/callbacks.rb:454:in `call'
/home/bdunne/.gem/ruby/2.3.3/gems/activesupport-5.0.4/lib/active_support/callbacks.rb:101:in `__run_callbacks__'
/home/bdunne/.gem/ruby/2.3.3/gems/activesupport-5.0.4/lib/active_support/callbacks.rb:750:in `_run_process_action_callbacks'
/home/bdunne/.gem/ruby/2.3.3/gems/activesupport-5.0.4/lib/active_support/callbacks.rb:90:in `run_callbacks'
/home/bdunne/.gem/ruby/2.3.3/gems/actionpack-5.0.4/lib/abstract_controller/callbacks.rb:19:in `process_action'
/home/bdunne/.gem/ruby/2.3.3/gems/actionpack-5.0.4/lib/action_controller/metal/rescue.rb:20:in `process_action'
/home/bdunne/.gem/ruby/2.3.3/gems/actionpack-5.0.4/lib/action_controller/metal/instrumentation.rb:32:in `block in process_action'
/home/bdunne/.gem/ruby/2.3.3/gems/activesupport-5.0.4/lib/active_support/notifications.rb:164:in `block in instrument'

Copy link
Member

Choose a reason for hiding this comment

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

Reverted by #15518

if defined?(Bundler)
Bundler.require(*Rails.groups, :ui_dependencies)
groups = ENV['BUNDLER_GROUPS'].split(",").collect(&:to_sym)
Copy link
Member

@Fryguy Fryguy Jun 30, 2017

Choose a reason for hiding this comment

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

You might want to tack in a .map(&:strip), just in case someone does a comma separated list and instinctively adds in commas

BUNDLER_GROUPS="rest_api, ui_dependencies"

Copy link
Member Author

Choose a reason for hiding this comment

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

Hmm, are you saying they'd put spaces after each comma?

Copy link
Member

Choose a reason for hiding this comment

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

Yes...it's possible...I do it all the time, instinctively.

Copy link
Member

Choose a reason for hiding this comment

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

Super proud of @Fryguy for not using collect here :trollface:

Copy link
Member

Choose a reason for hiding this comment

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

Believe it or not, I actually prefer map 😄

@Fryguy
Copy link
Member

Fryguy commented Jun 30, 2017

You want to explicitly disable the rubocop for Rails/Output since these are intentional.

@bdunne bdunne merged commit 8dc77d3 into ManageIQ:master Jul 5, 2017
@bdunne bdunne added the fine/no label Jul 5, 2017
@bdunne bdunne added this to the Sprint 64 Ending Jul 10, 2017 milestone Jul 5, 2017
jrafanie added a commit to jrafanie/manageiq-ui-classic that referenced this pull request Jul 6, 2017
This file pollutes the global namespace for legacy bad reasons.

Followup to bundler groups PR:
ManageIQ/manageiq#15459

The autoloading of ui constants in manageiq was reverted back here:
ManageIQ/manageiq#15518
jrafanie added a commit to jrafanie/manageiq that referenced this pull request Jul 6, 2017
See: ManageIQ/manageiq-ui-classic#1658

Followup to bundler groups PR:
ManageIQ#15459

The autoloading of ui constants in manageiq was reverted back here:
ManageIQ#15518
@jrafanie jrafanie deleted the bundler_groups_with_runner_script branch August 15, 2017 19:27
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.

None yet

6 participants