class MotherBrain::Upgrade::Worker

Upgrades a plugin by pinning cookbook versions and default attributes (based on the plugin components’ version attributes).

Attributes

environment_name[R]

@return [String]

job[R]

@return [MotherBrain::Job]

options[R]

@return [Hash]

plugin[R]

@return [MotherBrain::Plugin]

Public Class Methods

new(job, environment_name, plugin, options = {}) click to toggle source

@param [String] environment_name @param [MotherBrain::Plugin] plugin @param [MotherBrain::Job] job

@option options [Hash] component_versions

Hash of components and the versions to set them to

@option options [Hash] cookbook_versions

Hash of cookbooks and the versions to set them to

@option options [Hash] environment_attributes

any additional attributes to set on the environment

@option options [String] environment_attributes_file

any additional attributes to set on the environment via a json file

@option options [Boolean] :force

Force any locks to be overwritten
# File lib/mb/upgrade/worker.rb, line 40
def initialize(job, environment_name, plugin, options = {})
  @job              = job
  @environment_name = environment_name
  @plugin           = plugin
  @options          = options
end

Public Instance Methods

run() click to toggle source

@raise [ComponentNotFound] if a component version is passed that does

not have a corresponding component in the plugin

@raise [ComponentNotVersioned] if a component version is passed that

does not have a version attribute in the corresponding component

@raise [EnvironmentNotFound] if the environment does not exist

# File lib/mb/upgrade/worker.rb, line 54
def run
  job.set_status("Starting")
  job.report_running

  assert_environment_exists

  chef_synchronize(chef_environment: environment_name, force: options[:force], job: job) do
    if component_versions.any?
      job.set_status("Setting component versions")
      set_component_versions(environment_name, plugin, component_versions)
    end

    if cookbook_versions.any?
      job.set_status("Setting cookbook versions")
      set_cookbook_versions(environment_name, cookbook_versions)
    end

    if environment_attributes.any?
      job.set_status("Setting environment attributes")
      set_environment_attributes(environment_name, environment_attributes)
    end

    if environment_attributes_file
      job.set_status("Setting environment attributes from #{environment_attributes_file}")
      set_environment_attributes_from_file(environment_name, environment_attributes_file)
    end

    run_chef if nodes.any?
  end

  job.report_success
rescue => ex
  job.report_failure(ex)
ensure
  job.terminate if job && job.alive?
end

Private Instance Methods

assert_environment_exists() click to toggle source

@raise [EnvironmentNotFound]

# File lib/mb/upgrade/worker.rb, line 94
def assert_environment_exists
  unless chef_connection.environment.find(environment_name)
    raise EnvironmentNotFound.new(environment_name)
  end
end
bucket(nodes) click to toggle source

Places hosts into buckets. The buckets depend on whether the stack_order option is true. If it is true then the plugin is consulted to obtain the stack_order tasks and a bucket is created for each group in the bootstrap order. If the stack_order option is not true then one bucket will be created with all the nodes. If more than one group specifies the same host the duplicates will be removed. If the stack_order is true and more than one group specifies the same host it will appear in the bucket for the first group it is found in and will be removed from all others.

@example

 stack_order do
   bootstrap('some_component::db')
   bootstrap('some_component::app')
 end

 And given:
 {
   some_component => {
     db => ['db1', 'db2', 'db3'],
     app => ['app1', 'app2', 'app3']
   }
 }

Then with stack_order == true

  [['db1', 'db2', 'db3'],['app1','app2','app3']]

With stack_order == false

  [['db1', 'db2', 'db3', 'app1','app2','app3']]
# File lib/mb/upgrade/worker.rb, line 186
def bucket(nodes)
  if upgrade_in_stack_order?
    task_queue = plugin.bootstrap_routine.task_queue

    seen = []
    task_queue.collect do |task|
      component_name, group_name = task.group_name.split('::')
      group_nodes = nodes[component_name][group_name]
      group_nodes = group_nodes - seen
      seen += group_nodes
      group_nodes
    end
  else
    [] << nodes.collect do |component, groups|
      groups.collect do |group, nodes|
        nodes
      end
    end.flatten.compact.uniq
  end
end
component_versions() click to toggle source

@return [Hash]

# File lib/mb/upgrade/worker.rb, line 101
def component_versions
  options[:component_versions] || {}
end
cookbook_versions() click to toggle source

@return [Hash]

# File lib/mb/upgrade/worker.rb, line 106
def cookbook_versions
  options[:cookbook_versions] || plugin.cookbook_versions
end
environment_attributes() click to toggle source

@return [Hash]

# File lib/mb/upgrade/worker.rb, line 111
def environment_attributes
  options[:environment_attributes] || {}
end
environment_attributes_file() click to toggle source

@return [String, nil]

# File lib/mb/upgrade/worker.rb, line 116
def environment_attributes_file
  options[:environment_attributes_file]
end
max_concurrency() click to toggle source
# File lib/mb/upgrade/worker.rb, line 120
def max_concurrency
  options[:concurrency]
end
nodes() click to toggle source

@return [Array<String>]

# File lib/mb/upgrade/worker.rb, line 129
def nodes
  @nodes ||= begin
    job.set_status("Looking for nodes")

    nodes = plugin.nodes(environment_name)
    nodes.each do |component_name, group|
      group.each do |group_name, nodes|
        group[group_name] = nodes.map(&:public_hostname)
      end
    end

    log.info("Found nodes #{nodes.inspect}")
    nodes = bucket(nodes)
    nodes = slice_for_concurrency(nodes)

    log.info("Sliced nodes into concurrency buckets #{nodes.inspect}")

    unless nodes.any?
      log.info "No nodes in environment '#{environment_name}'"
    end

    nodes
  end
end
run_chef() click to toggle source
# File lib/mb/upgrade/worker.rb, line 225
def run_chef
  log.info "Running Chef on #{nodes}"
  job.set_status("Running Chef on nodes")

  nodes.map do |group|
    log.info("Running chef concurrently on nodes #{group.inspect}")
    group.concurrent_map do |node|
      node_querier.chef_run(node)
    end
  end
end
slice_for_concurrency(nodes) click to toggle source

Takes an array of buckets and slices the buckets based on the value of max_concurrency.

@example

With a max_concurrency of two and an input of
  [['db1', 'db2', 'db3'],['app1','app2','app3']]

Returns
  [['db1', 'db2'], ['db3'], ['app1','app2'], ['app3']]
# File lib/mb/upgrade/worker.rb, line 217
def slice_for_concurrency(nodes)
  if max_concurrency
    nodes.inject([]) { |buckets, g| buckets += g.each_slice(max_concurrency).to_a }
  else
    nodes
  end
end
upgrade_in_stack_order?() click to toggle source
# File lib/mb/upgrade/worker.rb, line 124
def upgrade_in_stack_order?
  options[:stack_order] == true
end