Yugendran
Yugendran

Reputation: 170

Chef guard block ruby code not executed at run time

According to Chef's two pass model any code inside ruby-block, guard blocks and lazy are executed only during run time.

I have a recipe with three resources.

Variables whose values are assigned during compile phase.

require 'socket'
require 'down'
require 'net/http'

conf = `hostname`.chomp
vpn_ip_list = Socket.ip_address_list.select{ |ip| ip.ip_address.match(/^10.12/) }
!vpn_ip_list.empty? ? ip_addr = vpn_ip_list.first.ip_address : ip_addr = ""

1st resource - Checks if desired VPN IP address is assigned using code in guard block and if not assigned downloads the conf file and notifies 2nd service resource to restart openvpn. The values for variables inside guard block are obtained during compiled phase.

ruby_block 'download_openvpn_conf' do
block do 
    attempt = 2
    begin
        retries ||= 0
        tempfile = Down.download("http://some-url1/#{conf}",max_redirects: 0)
        FileUtils.mv tempfile.path, "#{node['openvpn-conf-path']}/#{tempfile.original_filename}"
        FileUtils.chmod 0644, "#{node['openvpn-conf-path']}/#{tempfile.original_filename}"
    rescue Down::Error => e
        node.run_state['error'] = e.to_s
        puts e
        Chef::Log.warn ("\n \t ***********#{e}***********")
        retry if (retries += 1) < 1
    end 
end
only_if {vpn_ip_list.size.eql?(0) || vpn_ip_list.size >= 2}
action :run
notifies :restart, 'service[openvpn]', :immediately
notifies :delete, "file[#{node['openvpn-conf-path']}/#{conf}]", :before
end

2nd resource - Restarts the openvpn service. By this time the VPN IP is assigned to system.

service 'openvpn' do
supports :status => true, :restart => true, :start => true, :stop => true
action :nothing
Chef::Log.info ("\n \t *********Restarting OPEN-VPN*********")
end

Since the above service is executed during the converge phase, the VPN ip address should have been assigned based on the downloaded configuration file.

3rd resource - If the VPN IP is not assigned by the time of execution of 2nd resource, this places a request for new vpn conf file.

ruby_block 'manual_vpn' do
block do
    if node.run_state['error'] == "file not found"
        Chef::Log.info ("\n \t ******** VPN IP has not been assigned. File may be corrupted & initiating re-run********")
        uri = URI.parse("http://some-url2=#{host}&action=create")
        Chef::Log.info (uri)
        http = Net::HTTP.new(uri.host,uri.port)
        request = Net::HTTP::Get.new(uri.request_uri)
        response = http.request(request)
        case response.body
        when "error"
            Chef::Log.info ("\n \t Website reported an Error. Config for the given serial number could have already been generated")
        when "Request for vpn successfully added."  
            Chef::Log.warn ("\n \t **** Inside response processing => Request Accepted **")
            Chef::Log.warn ("\n \t *** New vpn config request has been accepted. Waiting for 3 minutes ***")
            sleep 180
        else
            Chef::Log.info ("\n \t Nothing to do - Extra option for future")    
        end 
    else 
        puts "Config file exists and hence not downloading"
    end     
end 
notifies :run, 'ruby_block[re-download-vpn-conf]', :delayed
only_if { Socket.ip_address_list.select{ |ip| ip.ip_address.match(/^10.12/) }.size.eql?(0) }
not_if {node.run_state['error'].eql?("too many redirects")}
end 

The VPN IP assigned is checked by the code in guard block only_if { Socket.ip_address_list.select{ |ip| ip.ip_address.match(/^10.12/) }.size.eql?(0) } and is supposed to be executed only at run time. By the end of 2nd resource execution the VPN IP is assigned for sure but code inside the above guard could not detect it.

I have used Pry debugger at the end of recipe within a test ruby block to verify that IP is assigned post the execution of 2nd service restart resource. Wondering why code inside the guard block of Chef is not able to detect the VPN assigned by the previous resource execution.

Upvotes: 1

Views: 711

Answers (1)

Yugendran
Yugendran

Reputation: 170

Basically the issue is Chef doesn't wait for the state of the system post service resource execution. Chef doesn't bother if the service is up and running, it just cares about the execution of the service.

It's a classic example of race condition with chef moving head to execute the next resource. To avoid this I had to introduce a desired sleep time in a ruby block. The other approach is to add a until true condition but that didn't work in my case as the ruby block in chef was stuck infinitely, though the service was up and running.

service 'openvpn' do
   supports :status => true, :restart => true, :start => true, :stop => true
   action :nothing
   Chef::Log.info ("\n \t *********Restarting OPEN-VPN*********")
end


ruby_block 'wait for vpn ip' do
    block do
        Chef::Log.warn ("****************** Sleep block starts ********************")
        sleep 30
        Chef::Log.warn ("****************** Sleep block exits ********************")
    end 
    action :run
end

ruby_block 'manual_vpn' do
block do
    if node.run_state['error'] == "file not found"
        Chef::Log.info ("\n \t ******** VPN IP has not been assigned. File may be corrupted & initiating re-run********")
        uri = URI.parse("http://some-url2=#{host}&action=create")
        Chef::Log.info (uri)
        http = Net::HTTP.new(uri.host,uri.port)
        request = Net::HTTP::Get.new(uri.request_uri)
        response = http.request(request)
        case response.body
        when "error"
            Chef::Log.info ("\n \t Website reported an Error. Config for the given serial number could have already been generated")
        when "Request for vpn successfully added."  
            Chef::Log.warn ("\n \t **** Inside response processing => Request Accepted **")
            Chef::Log.warn ("\n \t *** New vpn config request has been accepted. Waiting for 3 minutes ***")
            sleep 180
        else
            Chef::Log.info ("\n \t Nothing to do - Extra option for future")    
        end 
    else 
        puts "Config file exists and hence not downloading"
    end     
end 
notifies :run, 'ruby_block[re-download-vpn-conf]', :delayed
only_if { Socket.ip_address_list.select{ |ip| ip.ip_address.match(/^10.12/) }.size.eql?(0) }
not_if {node.run_state['error'].eql?("too many redirects")}
end  

Upvotes: 1

Related Questions