require "lib/knife/knife_factory" require "lib/puts_and_flush" require "hooks" require_relative "server_executor/expiration_scheduler" require_relative 'server_executor/server_operation_result' require "lib/ssh/ssh_utils" require "exceptions/bootstrap_error" require "exceptions/deploy_error" require "exceptions/deploy_info_error" require "exceptions/creation_error" require "db/mongo/models/image" require "db/mongo/models/server" require "db/mongo/models/key" require "db/mongo/models/project" module Devops module Executor class ServerExecutor include Hooks include PutsAndFlush include ServerOperationResultCode # waiting for 5*60 seconds (5 min) MAX_SSH_RETRIES_AMOUNT = 60 define_hook :before_create define_hook :after_create #params: # out - container for output data define_hook :before_bootstrap define_hook :after_bootstrap #before_deploy :add_run_list_to_deploy_info attr_accessor :server, :environment, :job_task, :project attr_reader :out, :current_user def initialize server, out, current_user, options={} if server @project = Devops::Model::Project.find_with_environment(server.project, server.environment) @environment = @project.environment(server.environment) @category = @environment.get_category(server.category) @server = server end @out = out @out.class.send(:define_method, :flush) { } unless @out.respond_to?(:flush) @current_user = current_user end # TODO: refactore attr_accessors to constructor options, # because these values don't change after object initializing def job_task= task @job_task = task end def project= p @project = p end def environment= e @environment = e end def server @server end def category= category @category = category end def create_server_object options Devops::Model::Server.new({ "project" => @project.id, "environment" => @environment.id, "category" => @category.id, "created_by" => @current_user, "provider" => @category.provider.name, "provider_account" => @category.provider.account, "private_ip" => options["private_ip"] }) end def create_server options @server = create_server_object(options) @server.set_last_operation(Devops::Model::Server::OperationType::CREATION, @current_user, @job_task.id) provider = @category.provider @out.puts "Create server..." @server.run_list = options["run_list"] || [] @server.ssh_key = options["ssh_key"] || provider.account_instance.ssh_key @server.name = options["name"] || @server.provider_instance.create_default_server_name(@server) image = Devops::Model::Image.where(image_id: provider.image, provider: provider.name, provider_account: provider.account).first @server.remote_user = image.remote_user res = {} @out << "\nBefore create hooks...\n" res[:before] = self.run_hook :before_create @out << "Done\n" @out.puts "Running cloud instance...\nUsing provider '#{provider.name}' and account '#{provider.account}'\n" @out.flush unless provider.create_server(@server, @out) raise Devops::Exception::CreationError.new("Can not create server in cloud with provider '#{provider.name}' and account '#{provider.account}'") end @server.save provider.waiting_server @server, @out @server.save @out << @server.info @out.flush @out << "\n\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" schedule_expiration() 0 end def deploy_info options @server.set_last_operation(Devops::Model::Server::OperationType::DEPLOY_INFO, @current_user, @job_task.id) options[:deploy_info] || @project.deploy_info(@environment) end def bootstrap_server options, deploy_info @server.set_last_operation(Devops::Model::Server::OperationType::BOOTSTRAP, @current_user, @job_task.id) =begin <<<<<<< HEAD if @project.is_sandbox? bootstrap_options[:deployers] = [options['created_by']] bootstrap_options[:deployers] += (options['project_info']['deployers'] || []) if options['project_info'] end ======= =end @job_task.bootstrap_info = @category.cm_tool.bootstrap_instance(@server, deploy_info, @out, options) @job_task.save @server.save DevopsLogger.logger.info "Server #{@server.id} has been bootstraped with cm name '#{@server.cm_name}'" 0 end def deploy_server options, deploy_info @server.set_last_operation(Devops::Model::Server::OperationType::DEPLOY, @current_user, @job_task.id) deploy_info[:run_list] = compute_run_list @job_task.deploy_info = deploy_info @job_task.save @category.cm_tool.deploy_instance(@server, deploy_info, @out) DevopsLogger.logger.info "Server #{@server.id} has been deployed" 0 end def unbootstrap_server res = @category.cm_tool.delete_instance(@server, @out) res end def deploy_server_with_tags tags, deploy_info unless tags.empty? deploy_info[tags: tags] end @job_task.deploy_info = deploy_info @job_task.save r = @category.cm_tool.deploy_instance(@server, deploy_info, @out) @server.set_last_operation(Devops::Model::Server::OperationType::DEPLOY, @current_user, @job_task.id) @server.save return r end def delete_server if @server.static? @out << "'#{@server.id}' is a static server" @out.flush unless @server.cm_name.nil? unbootstrap_server end @server.delete msg = "Static server '#{@server.id}' is removed" DevopsLogger.logger.info msg return msg, nil end puts_and_flush "'#{@server.id}' is not a static server" puts_and_flush @server.to_hash @category.cm_tool.delete_instance(@server, @out) provider = @server.provider_instance msg = nil begin msg = provider.delete_server @server rescue Fog::Compute::OpenStack::NotFound, Fog::Compute::AWS::NotFound msg = "Server with id '#{@server.id}' not found in '#{provider.name}' servers" DevopsLogger.logger.warn msg end @out.puts msg @server.delete info = "Server '#{@server.id}' with name '#{@server.name}' for project '#{@server.project}-#{@server.environment}' is removed" puts_and_flush info DevopsLogger.logger.info info return 0 end def roll_back @out.puts "Trying to roll back..." unless @server.id.nil? unless @server.name.nil? @out.puts "Server '#{@server.name}' with id '#{@server.id}' is not created" @category.cm_tool.delete_instance(@server, @out) end begin @out.puts @server.provider_instance.delete_server(@server) rescue => e @out.puts e.message end @out << "\nRolled back\n" end end def add_run_list_to_deploy_info out, deploy_info out << "\nGenerate run list hook...\n" if deploy_info["run_list"] out << "Deploy info already contains 'run_list': #{deploy_info["run_list"].join(", ")}\n" return end out << "Project run list: #{@project.run_list.join(", ")}\n" out << "Deploy environment run list: #{@environment.run_list.join(", ")}\n" out << "Server run list: #{@server.run_list.join(", ")}\n" deploy_info["run_list"] = compute_run_list out << "New deploy run list: #{deploy_info["run_list"].join(", ")}\nRun list has been generated\n\n" end def compute_run_list rlist = [] [@server.provider_instance.run_list, @project.run_list, @environment.run_list, @server.run_list].each do |sub_run_list| rlist += sub_run_list if sub_run_list.is_a?(Array) end if @server.stack #TODO stack = Devops::Db.connector.stack(@server.stack) srl = stack.run_list rlist += srl if srl end rlist.uniq end def waiting_ssh ip = @server.private_ip unless @server.public_ip.nil? ip = server.public_ip @out.puts "\nPublic IP is present" end @out << "Waiting for SSH" k = Devops::Model::Key.find(@server.ssh_key) retries_amount = 0 begin sleep(5) res = Devops::SSH::Utils.try_ssh(ip, @server.remote_user, k.path) if res @out.puts "\n" @out.flush return true else retries_amount += 1 if retries_amount > MAX_SSH_RETRIES_AMOUNT raise Devops::Exception::BootstrapError.new("Can not connect to #{@server.remote_user}@#{ip}") end raise ArgumentError.new end rescue ArgumentError => e @out << "." @out.flush retry end end private def schedule_expiration if @environment.expires job_id = ExpirationScheduler.new(@environment.expires, @server).schedule_expiration! puts_and_flush "Planning expiration in #{@environment.expires}, job_id: #{job_id}" end end end end end