Class: ChefWorkflow::VM::KnifeProvisioner

Inherits:
Object
  • Object
show all
Includes:
DebugSupport, KnifePluginSupport
Defined in:
lib/chef-workflow/support/vm/knife.rb

Overview

The Knife Provisioner does three major things:

  • Bootstraps a series of machines living on IP addresses supplied to it

  • Ensures that they converged successfully (if not, raises and displays output)

  • Waits until chef has indexed their metadata

On deprovision, it deletes the nodes and clients related to this server group.

Machines are named as such: $server_group-$number, where $number starts at 0 and increases with the number of servers requested. Your node names will be named this as well as the clients associated with them.

It does as much of this as it can in parallel, but stalls the current thread until the subthreads complete. This allows is to work as quickly as possible in a ‘serial’ scheduling scenario as we know bootstrapping can always occur in parallel for the group.

Constant Summary

Constants included from DebugSupport

DebugSupport::CHEF_WORKFLOW_DEBUG_DEFAULT

Instance Attribute Summary collapse

Instance Method Summary collapse

Methods included from KnifePluginSupport

#init_knife_plugin

Methods included from DebugSupport

#if_debug

Constructor Details

#initializeKnifeProvisioner

constructor.



54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/chef-workflow/support/vm/knife.rb', line 54

def initialize
  require 'chef/node'
  require 'chef/search/query'
  require 'chef/knife/ssh'
  require 'chef/knife/bootstrap'
  require 'chef-workflow/support/knife'
  require 'timeout'

  @ips            = []
  @username       = nil
  @password       = nil
  @ssh_key        = nil
  @port           = nil
  @use_sudo       = nil
  @run_list       = nil
  @template_file  = nil
  @environment    = nil
  @solr_check     = true
end

Instance Attribute Details

#environmentObject

the chef environment to be used.



40
41
42
# File 'lib/chef-workflow/support/vm/knife.rb', line 40

def environment
  @environment
end

#ipsObject

the list of IPs to provision.



44
45
46
# File 'lib/chef-workflow/support/vm/knife.rb', line 44

def ips
  @ips
end

#nameObject

the name of this server group.



48
49
50
# File 'lib/chef-workflow/support/vm/knife.rb', line 48

def name
  @name
end

#passwordObject

the password for SSH.



32
33
34
# File 'lib/chef-workflow/support/vm/knife.rb', line 32

def password
  @password
end

#portObject

the port to contact for SSH



42
43
44
# File 'lib/chef-workflow/support/vm/knife.rb', line 42

def port
  @port
end

#run_listObject

the run list of this server group.



46
47
48
# File 'lib/chef-workflow/support/vm/knife.rb', line 46

def run_list
  @run_list
end

#solr_checkObject

perform the solr check to ensure the instance has converged and its metadata is ready for searching.



51
52
53
# File 'lib/chef-workflow/support/vm/knife.rb', line 51

def solr_check
  @solr_check
end

#ssh_keyObject

the ssh key to be used for SSH



36
37
38
# File 'lib/chef-workflow/support/vm/knife.rb', line 36

def ssh_key
  @ssh_key
end

#template_fileObject

the bootstrap template to be used.



38
39
40
# File 'lib/chef-workflow/support/vm/knife.rb', line 38

def template_file
  @template_file
end

#use_sudoObject

drive knife bootstrap’s sudo functionality.



34
35
36
# File 'lib/chef-workflow/support/vm/knife.rb', line 34

def use_sudo
  @use_sudo
end

#usernameObject

the username for SSH.



30
31
32
# File 'lib/chef-workflow/support/vm/knife.rb', line 30

def username
  @username
end

Instance Method Details

#bootstrap(node_name, ip) ⇒ Object

Bootstraps a single node. Validates bootstrap by checking the node metadata directly and ensuring it made it into the chef server.



178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
# File 'lib/chef-workflow/support/vm/knife.rb', line 178

def bootstrap(node_name, ip)
  args = []

  args += %W[-x #{username}]                    if username
  args += %W[-P #{password}]                    if password
  args += %w[--sudo]                            if use_sudo
  args += %W[-i #{ssh_key}]                     if ssh_key
  args += %W[--template-file #{template_file}]  if template_file
  args += %W[-p #{port}]                        if port
  args += %W[-E #{environment}]                 if environment

  args += %W[-r #{run_list.join(",")}]
  args += %W[-N '#{node_name}']
  args += [ip]

  bootstrap_cli = init_knife_plugin(Chef::Knife::Bootstrap, args)

  Thread.new do
    begin
      bootstrap_cli.run
    rescue SystemExit => e
      # welp, looks like they finally fixed it.
      # can't rely on it for compat reasons, but at least we're not
      # dropping to a prompt when a bootstrap fails.
    end
    # knife bootstrap is the honey badger when it comes to exit status.
    # We can't rely on it, so we examine the run_list of the node instead
    # to ensure it converged.
    node = Chef::Node.load(node_name) rescue nil
    run_list_size = node ? (node.run_list.to_a.size rescue 0) : 0
    unless run_list_size > 0
      puts bootstrap_cli.ui.stdout.string
      puts bootstrap_cli.ui.stderr.string

      if node
        #
        # hack for bad first-converges that preserves the run list.
        #
        @run_list.each { |i| node.run_list.add(i) unless node.run_list.include?(i) }
        node.save
      end

      raise "bootstrap for #{node_name}/#{ip} wasn't successful."
    end
    if_debug(2) do
      puts bootstrap_cli.ui.stdout.string
      puts bootstrap_cli.ui.stderr.string
    end
  end
end

#check_nodesObject

Checks that the nodes have made it into the search index. Will block until all nodes in this server group are found, or a 60 second timeout is reached, at which point it will raise.



137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
# File 'lib/chef-workflow/support/vm/knife.rb', line 137

def check_nodes
  q = Chef::Search::Query.new
  unchecked_node_names = @node_names.to_a

  # this dirty hack turns 'role[foo]' into 'roles:foo', but also works on
  # recipe[] too. Then joins the whole thing with AND
  search_query = run_list.
    map { |s| s.gsub(/\[/, 's:"').gsub(/\]/, '"') }.
    join(" AND ")

  Timeout.timeout(ChefWorkflow::KnifeSupport.search_index_wait) do
    until unchecked_node_names.empty?
      node_name = unchecked_node_names.shift
      if_debug(3) do
        $stderr.puts "Checking search validity for node #{node_name}"
      end

      result = q.search(
        :node,
        search_query + %Q[ AND name:"#{node_name}"]
      ).first

      unless result and result.count == 1 and result.first.name == node_name
        unchecked_node_names << node_name
      end

      # unfortunately if this isn't here you might as well issue kill -9 to
      # the rake process
      sleep 0.3
    end
  end

  return true
rescue Timeout::Error
  raise "Bootstrapped nodes for #{name} did not appear in Chef search index after 60 seconds."
end

#client_delete(node_name) ⇒ Object

Deletes a chef client.



232
233
234
235
# File 'lib/chef-workflow/support/vm/knife.rb', line 232

def client_delete(node_name)
  require 'chef/knife/client_delete'
  init_knife_plugin(Chef::Knife::ClientDelete, [node_name, '-y']).run
end

#init_nodes_dbObject



74
75
76
# File 'lib/chef-workflow/support/vm/knife.rb', line 74

def init_nodes_db
  @node_names = ChefWorkflow::DatabaseSupport::Set.new('nodes', name)
end

#node_delete(node_name) ⇒ Object

Deletes a chef node.



240
241
242
243
# File 'lib/chef-workflow/support/vm/knife.rb', line 240

def node_delete(node_name)
  require 'chef/knife/node_delete'
  init_knife_plugin(Chef::Knife::NodeDelete, [node_name, '-y']).run
end

#reportObject



245
246
247
248
249
250
251
252
253
# File 'lib/chef-workflow/support/vm/knife.rb', line 245

def report
  res = ["nodes:"]

  ChefWorkflow::IPSupport.get_role_ips(name).each_with_index do |ip, i|
    res += ["#{name}-#{i}: #{ip}"]
  end

  return res
end

#shutdownObject

Deprovisions the server group. Runs node delete and client delete on all nodes that were created by this provisioner.



111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# File 'lib/chef-workflow/support/vm/knife.rb', line 111

def shutdown
  t = []

  init_nodes_db

  @node_names.each do |node_name|
    t.push(
      Thread.new do
        client_delete(node_name) rescue nil
        node_delete(node_name) rescue nil
      end
    )
  end

  t.each(&:join)

  @node_names.clear

  return true
end

#startup(*args) ⇒ Object

Runs the provisioner. Accepts an array of IP addresses as its first argument, intended to be provided by provisioners that ran before it as their return value.

Will raise if the IPs are not supplied or the provisioner is not named with a server group.



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/chef-workflow/support/vm/knife.rb', line 86

def startup(*args)
  @ips = args.first #argh
  raise "This provisioner is unnamed, cannot continue" unless name
  raise "This provisioner requires ip addresses which were not supplied" unless ips

  init_nodes_db

  @run_list ||= ["role[#{name}]"]

  t = []
  ips.each_with_index do |ip, index|
    node_name = "#{name}-#{index}"
    @node_names.add(node_name)
    t.push bootstrap(node_name, ip)
  end

  t.each(&:join)

  return solr_check ? check_nodes : true
end