fluke/devops-service/lib/executors/server_executor.rb

353 lines
12 KiB
Ruby

require "lib/knife/knife_factory"
require "hooks"
module Devops
module Executor
class ServerExecutor
include Hooks
#params:
# out - container for output data
# deploy_info - hash with deploy data
define_hook :before_deploy
define_hook :after_deploy
define_hook :before_create
define_hook :after_create
#params:
# out - container for output data
define_hook :before_bootstrap
define_hook :after_bootstrap
def initialize server, out
if server
@project = Devops::Db.connector.project(server.project)
@deploy_env = @project.deploy_env(server.deploy_env)
end
@knife_instance = KnifeFactory.instance
@server = server
@out = out
@out.class.send(:define_method, :flush) { } unless @out.respond_to?(:flush)
end
def project= p
@project = p
end
def deploy_env= e
@deploy_env = e
end
def server
@server
end
def create_server options
@server = Devops::Model::Server.new({"project" => @project.id, "deploy_env" => @deploy_env.identifier, "created_by" => options["created_by"], "provider" => @deploy_env.provider})
provider = @server.provider_instance
mongo = ::Devops::Db.connector
begin
@out << "Create server...\n"
@out.flush
@server.run_list = options["run_list"] || []
@server.chef_node_name = options["name"]
@server.key = options["key"] || provider.ssh_key
i = mongo.image(@deploy_env.image)
@server.remote_user = i.remote_user
res = {}
@out << "\nBefore create hooks...\n"
res[:before] = self.run_hook :before_create
@out << "Done\n"
return false unless provider.create_server(@server, @deploy_env.image, @deploy_env.flavor, @deploy_env.subnets, @deploy_env.groups, @out)
mongo.server_insert @server
@out << "\nAfter create hooks...\n"
res[:after] = self.run_hook :after_create
@out << "Done\n"
@out.flush
DevopsLogger.logger.info "Server with parameters: #{@server.to_hash.inspect} is running"
unless options["without_bootstrap"]
@server.run_list = Set.new.merge(@project.run_list).merge(@deploy_env.run_list).merge(@server.run_list).to_a
@server.chef_node_name = provider.create_default_chef_node_name(@server) if @server.chef_node_name.nil?
bootstrap_options = {
bootstrap_template: i.bootstrap_template
}
return two_phase_bootstrap(bootstrap_options)
else
return 0
end
rescue => e
DevopsLogger.logger.error e.message
roll_back
mongo.server_delete @server.id
return 5
end
end
def bootstrap options
@out << "\n\nBootstrap...\n"
@out.flush
k = Devops::Db.connector.key(@server.key)
cert_path = k.path
options[:cert_path] = k.path
@out << "\nBefore bootstrap hooks...\n"
res = self.run_hook(:before_bootstrap, @out)
@out << "Done\n"
if @server.private_ip.nil?
@out << "Error: Private IP is null"
return false
end
ja = {
:provider => @server.provider,
:devops_host => `hostname`.strip
}
ip = @server.private_ip
unless @server.public_ip.nil?
ip = @server.public_ip
@out << "\nPublic IP is present\n"
end
@out << "\nWaiting for SSH..."
@out.flush
i = 0
cmd = "ssh -i #{cert_path} -q #{@server.remote_user}@#{ip} 'exit'"
@out << "Test command: '#{cmd}'\n"
cmd << " 2>&1"
begin
sleep(5)
res = `#{cmd}`
i += 1
if i == 120
@out << "\nCan not connect to #{@server.remote_user}@#{ip}"
@out << "\n" + res
DevopsLogger.logger.error "Can not connect with command 'ssh -i #{cert_path} #{@server.remote_user}@#{ip}':\n#{res}"
return false
end
raise ArgumentError.new("Can not connect with command '#{cmd}' ") unless $?.success?
rescue ArgumentError => e
retry
end
r = @knife_instance.knife_bootstrap(@out, ip, self.bootstrap_options(ja, options))
if r == 0
@out << "Chef node name: #{@server.chef_node_name}\n"
::Devops::Db.connector.server_set_chef_node_name @server
@out << "Chef node name has been updated\n"
@out << "After bootstrap hooks...\n"
res = self.run_hook(:after_bootstrap, @out)
@out << "Done\n"
DevopsLogger.logger.info "Server with id '#{@server.id}' is bootstraped"
else
end
@out.flush
r
end
def bootstrap_options attributes, options
bootstrap_options = [
"-x #{@server.remote_user}",
"-i #{options[:cert_path]}",
"--json-attributes '#{attributes.to_json}'"
]
bootstrap_options.push "-N #{@server.chef_node_name}" if @server.chef_node_name
bootstrap_options.push "--sudo" unless @server.remote_user == "root"
bootstrap_options.push "-t #{options[:bootstrap_template]}" if options[:bootstrap_template]
rl = options[:run_list]
bootstrap_options.push "-r #{rl.join(",")}" unless rl.nil?# rl.empty?
bootstrap_options.push "-c #{options[:config]}" if options[:config]
bootstrap_options
end
def two_phase_bootstrap options
provider = @server.provider_instance
mongo = ::Devops::Db.connector
options[:run_list] = provider.run_list
status = bootstrap(options)
if status == 0
if check_server
@out << "Server #{@server.chef_node_name} is created"
else
@out << roll_back
mongo.server_delete @server.id
return 5
end
@out << "\n"
@out.flush
run_list = @server.run_list + provider.run_list
@out << "\nRun list: #{run_list.inspect}"
# s.options[:run_list] += run_list
@knife_instance.set_run_list(@server.chef_node_name, run_list)
status = deploy_server({})
if status != 0
msg = "Failed on chef-client with project run list, server with id '#{@server.id}'"
DevopsLogger.logger.error msg
@out << "\n" + msg + "\n"
end
else
msg = "Failed while bootstraping server with id '#{@server.id}'"
DevopsLogger.logger.error msg
@out << "\n" + msg + "\n"
# @out << roll_back
# mongo.server_delete @server.id
end
return status
end
def check_server
@knife_instance.chef_node_list.include?(@server.chef_node_name) and @knife_instance.chef_client_list.include?(@server.chef_node_name)
end
def unbootstrap
k = Devops::Db.connector.key(@server.key)
cert_path = k.path
i = 0
begin
r = `ssh -i #{cert_path} -q #{@server.remote_user}@#{@server.private_ip} rm -Rf /etc/chef`
raise(r) unless $?.success?
rescue => e
DevopsLogger.logger.error "Unbootstrap error: " + e.message
i += 1
sleep(1)
retry unless i == 5
return e.message
end
nil
end
def deploy_server_with_tags tags, deploy_info
old_tags_str = nil
new_tags_str = nil
unless tags.empty?
old_tags_str = @knife_instance.tags_list(@server.chef_node_name).join(" ")
@out << "Server tags: #{old_tags_str}\n"
@knife_instance.tags_delete(@server.chef_node_name, old_tags_str)
new_tags_str = tags.join(" ")
@out << "Server new tags: #{new_tags_str}\n"
cmd = @knife_instance.tags_create(@server.chef_node_name, new_tags_str)
unless cmd[1]
m = "Error: Cannot add tags '#{new_tags_str}' to server '#{@server.chef_node_name}'"
DevopsLogger.logger.error(m)
@out << m + "\n"
return 3
end
DevopsLogger.logger.info("Set tags for '#{@server.chef_node_name}': #{new_tags_str}")
end
r = deploy_server deploy_info
unless tags.empty?
@out << "Restore tags\n"
cmd = @knife_instance.tags_delete(@server.chef_node_name, new_tags_str)
DevopsLogger.logger.info("Deleted tags for #{@server.chef_node_name}: #{new_tags_str}")
cmd = @knife_instance.tags_create(@server.chef_node_name, old_tags_str)
DevopsLogger.logger.info("Set tags for #{@server.chef_node_name}: #{old_tags_str}")
end
return r
end
def deploy_server deploy_info
@out << "\nBefore deploy hooks...\n"
res = self.run_hook(:before_deploy, @out, deploy_info)
@out << "Done\n"
@out << "\nRun chef-client on '#{@server.chef_node_name}'\n"
cmd = "chef-client --no-color"
if deploy_info["use_json_file"]
deploy_info.delete("use_json_file")
@out << "Build information:\n"
json = JSON.pretty_generate(deploy_info)
@out << json
@out << "\n"
file = "#{@server.project}_#{@server.deploy_env}_#{Time.new.to_i}"
dir = DevopsConfig.config[:project_info_dir]
File.open(File.join(dir, file), "w") do |f|
f.write json
end
@out.flush
cmd << " -j http://#{DevopsConfig.config[:address]}:#{DevopsConfig.config[:port]}/#{DevopsConfig.config[:url_prefix]}/v2.0/deploy/data/#{file}"
end
ip = if @server.public_ip.nil?
@server.private_ip
else
@out << "Public IP detected\n"
@server.public_ip
end
@out.flush
k = Devops::Db.connector.key(@server.key)
lline = @knife_instance.ssh_stream(@out, cmd, ip, @server.remote_user, k.path)
r = /Chef\sClient\sfinished/i
if lline[r].nil?
1
else
@out << "\nAfter deploy hooks...\n"
res = self.run_hook(:after_deploy, @out, deploy_info)
@out << "Done\n"
0
end
end
def delete_from_chef_server node_name
{
:chef_node => @knife_instance.chef_node_delete(node_name),
:chef_client => @knife_instance.chef_client_delete(node_name)
}
end
=begin
def delete_etc_chef s, cert_path
cmd = "ssh -i #{cert_path} -t -q #{s.remote_user}@#{s.private_ip}"
cmd += " sudo " unless s.remote_user == "root"
cmd += "rm -Rf /etc/chef"
r = `#{cmd}`
raise(r) unless $?.success?
end
=end
def delete_server
mongo = ::Devops::Db.connector
if @server.static?
if !@server.chef_node_name.nil?
unbootstrap
end
mongo.server_delete @server.id
msg = "Static server '#{@server.id}' is removed"
DevopsLogger.logger.info msg
return msg, nil
end
r = delete_from_chef_server(@server.chef_node_name)
provider = @server.provider_instance
begin
r[:server] = provider.delete_server @server
rescue Fog::Compute::OpenStack::NotFound, Fog::Compute::AWS::NotFound
r[:server] = "Server with id '#{@server.id}' not found in '#{provider.name}' servers"
DevopsLogger.logger.warn r[:server]
end
mongo.server_delete @server.id
info = "Server '#{@server.id}' with name '#{@server.chef_node_name}' for project '#{@server.project}-#{@server.deploy_env}' is removed"
DevopsLogger.logger.info info
r.each{|key, log| DevopsLogger.logger.info("#{key} - #{log}")}
return info, r
end
def roll_back
unless @server.id.nil?
@out << "Server '#{@server.chef_node_name}' with id '#{@server.id}' is not created\n"
@out << delete_from_chef_server(@server.chef_node_name).values.join("\n")
begin
@out << @server.provider_instance.delete_server(@server)
rescue => e
@out << e.message
end
@out << "\nRolled back\n"
end
end
end
end
end