require 'rufus-scheduler' require "uri" require "commands/status" require "commands/server" require "commands/bootstrap_templates" require "commands/knife_commands" require "providers/provider_factory" require "db/mongo/models/server" require "workers/create_server_worker" require "workers/bootstrap_worker" module Devops module API2_0 module Handler class Server extend StatusCommands extend ServerCommands extend BootstrapTemplatesCommands scheduler = Rufus::Scheduler.new def servers fields, reserved Devops::DB.connector.servers(nil, nil, nil, reserved, fields).map {|s| s.to_hash} end def chef_servers KnifeCommands.chef_node_list end def provider_servers provider ::Provider::ProviderFactory.get(provider).servers end def self.get_server lambda { check_privileges("server", "r") json Server.get_server_by_key(params[:name], params[:key]).to_hash } end def self.delete_server lambda { check_privileges("server", "w") body = create_object_from_json_body(Hash, true) key = (body.nil? ? nil : body["key"]) s = Server.get_server_by_key(params[:id], key) ### Authorization settings.mongo.check_project_auth s.project, s.deploy_env, request.env['REMOTE_USER'] info, r = delete_server(s, settings.mongo, logger) create_response(info, r) } end def self.create_server lambda { check_privileges("server", "w") body = create_object_from_json_body user = request.env['REMOTE_USER'] project_name = check_string(body["project"], "Parameter 'project' must be a not empty string") env_name = check_string(body["deploy_env"], "Parameter 'deploy_env' must be a not empty string") server_name = check_string(body["name"], "Parameter 'name' should be null or not empty string", true) without_bootstrap = body["without_bootstrap"] halt_response("Parameter 'without_bootstrap' should be a null or true") unless without_bootstrap.nil? or without_bootstrap == true force = body["force"] halt_response("Parameter 'force' should be a null or true") unless force.nil? or force == true groups = check_array(body["groups"], "Parameter 'groups' should be null or not empty array of string", String, true) key_name = check_string(body["key"], "Parameter 'key' should be null or not empty string", true) new_key = settings.mongo.key(key_name) unless key_name.nil? p = settings.mongo.check_project_auth(project_name, env_name, user) env = p.deploy_env(env_name) provider = ::Provider::ProviderFactory.get(env.provider) Server.check_chef_node_name(server_name, provider) unless server_name.nil? unless groups.nil? buf = groups - provider.groups.keys halt_response("Invalid security groups '#{buf.join("', '")}' for provider '#{provider.name}'") if buf.empty? end servers = Server.extract_servers(provider, p, env, body, user, settings.mongo) if body.key?("trace") stream() do |out| begin status = [] servers.each do |s| res = create_server_proc.call(out, s, provider, settings.mongo) status.push res end out << create_status(status) rescue IOError => e logger.error e.message end end else dir = DevopsConfig[:report_dir_v2] files = [] uri = URI.parse(request.url) servers.each do |s| h = s.to_hash h["options"] = s.options jid = CreateServerWorker.perform_async(dir, env.provider, h, request.env['REMOTE_USER'], DevopsConfig.config) Worker.set_status jid, Worker::STATUS::IN_QUEUE logger.info "Job '#{jid}' has been started" uri.path = "#{DevopsConfig[:url_prefix]}/v2.0/report/" + jid files.push uri.to_s end sleep 1 json files end } end def self.pause_server lambda { check_privileges("server", "w") body = create_object_from_json_body(Hash, true) key = (body.nil? ? nil : body["key"]) s = Server.get_server_by_key(params[:node_name], key) ## Authorization settings.mongo.check_project_auth s.project, s.deploy_env, request.env['REMOTE_USER'] provider = ::Provider::ProviderFactory.get(s.provider) r = provider.pause_server s if r.nil? create_response("Server with instance ID '#{s.id}' and node name '#{params[:node_name]}' is paused") else halt_response("Server with instance ID '#{s.id}' and node name '#{params[:node_name]}' can not be paused, It in state '#{r}'", 409) end } end def self.unpause_server lambda { check_privileges("server", "w") body = create_object_from_json_body(Hash, true) key = (body.nil? ? nil : body["key"]) s = Server.get_server_by_key(params[:node_name], key) ## Authorization settings.mongo.check_project_auth s.project, s.deploy_env, request.env['REMOTE_USER'] provider = ::Provider::ProviderFactory.get(s.provider) r = provider.unpause_server s if r.nil? create_response("Server with instance ID '#{s.id}' and node name '#{params[:node_name]}' is unpaused") else halt_response("Server with instance ID '#{s.id}' and node name '#{params[:node_name]}' can not be unpaused, It in state '#{r}'", 409) end } end def self.reserve_server lambda { check_privileges("server", "w") body = create_object_from_json_body(Hash, true) key = (body.nil? ? nil : body["key"]) s = Server.get_server_by_key(params[:node_name], key) user = request.env['REMOTE_USER'] settings.mongo.check_project_auth s.project, s.deploy_env, user halt_response(400, "Server '#{params[:node_name]}' already reserved") unless s.reserved_by.nil? s.reserved_by = user settings.mongo.server_update(s) create_response("Server '#{params[:node_name]}' has been reserved") } end def self.unreserve_server lambda { check_privileges("server", "w") body = create_object_from_json_body(Hash, true) key = (body.nil? ? nil : body["key"]) s = Server.get_server_by_key(params[:node_name], key) settings.mongo.check_project_auth s.project, s.deploy_env, request.env['REMOTE_USER'] halt_response(400, "Server '#{params[:node_name]}' is not reserved") if s.reserved_by.nil? s.reserved_by = nil settings.mongo.server_update(s) create_response("Server '#{params[:node_name]}' has been unreserved") } end # TODO: check bootstrap template name def self.bootstrap_server lambda { check_privileges("server", "w") body = create_object_from_json_body(Hash, true) id = check_string(body["instance_id"], "Parameter 'instance_id' must be a not empty string") name = check_string(body["name"], "Parameter 'name' should be a not empty string", true) rl = check_array(body["run_list"], "Parameter 'run_list' should be a not empty array of string", String, true) unless rl.nil? validator = Validators::Helpers::RunList.new(rl) halt_response(validator.message) unless validator.valid? end t = check_string(body["bootstrap_template"], "Parameter 'bootstrap_template' should be a not empty string", true) s = settings.mongo.server_by_instance_id(id) p = settings.mongo.check_project_auth s.project, s.deploy_env, request.env['REMOTE_USER'] d = p.deploy_env s.deploy_env provider = ::Provider::ProviderFactory.get(s.provider) Server.check_chef_node_name(name, provider) unless name.nil? s.options = { :run_list => rl || d.run_list, } unless t.nil? templates = get_templates halt_response("Invalid bootstrap template '#{t}', available values: #{templates.join(", ")}", 400) unless templates.include?(t) s.options[:bootstrap_template] = t end s.chef_node_name = name || provider.create_default_chef_node_name(s) logger.debug "Chef node name: '#{s.chef_node_name}'" status = [] if body.key?("trace") stream() do |out| begin cert = settings.mongo.key s.key logger.debug "Bootstrap certificate path: #{cert.path}" bootstrap s, out, cert.path, logger str = nil r = if check_server(s) settings.mongo.server_set_chef_node_name s str = "Server with id '#{s.id}' is bootstraped" logger.info str 0 else str = "Server with id '#{s.id}' is not bootstraped" logger.warn str 1 end status.push r out << str out << "\n" out << create_status(status) rescue IOError => e logger.error e.message end end else dir = DevopsConfig[:report_dir_v2] files = [] uri = URI.parse(request.url) h = s.to_hash h["options"] = s.options h["_id"] = s.id jid = BootstrapWorker.perform_async(dir, d.provider, h, request.env['REMOTE_USER'], DevopsConfig.config) Worker.set_status jid, Worker::STATUS::IN_QUEUE logger.info "Job '#{jid}' has been started" uri.path = "#{DevopsConfig[:url_prefix]}/v2.0/report/" + jid uri.query = nil uri.fragment = nil files.push uri.to_s sleep 1 json files end } end def self.add_server lambda { check_privileges("server", "w") body = create_object_from_json_body project = check_string(body["project"], "Parameter 'project' must be a not empty string") deploy_env = check_string(body["deploy_env"], "Parameter 'deploy_env' must be a not empty string") key = check_string(body["key"], "Parameter 'key' must be a not empty string") remote_user = check_string(body["remote_user"], "Parameter 'remote_user' must be a not empty string") private_ip = check_string(body["private_ip"], "Parameter 'private_ip' must be a not empty string") public_ip = check_string(body["public_ip"], "Parameter 'public_ip' should be a not empty string", true) p = settings.mongo.check_project_auth project, deploy_env, request.env['REMOTE_USER'] d = p.deploy_env(deploy_env) cert = settings.mongo.key(key) provider = ::Provider::ProviderFactory.get("static") s = Devops::Model::Server.new s.provider = provider.name s.project = project s.deploy_env = deploy_env s.remote_user = remote_user s.private_ip = private_ip s.public_ip = public_ip s.static = true s.id = "static_#{cert.id}-#{Time.now.to_i}" s.key = cert.id settings.mongo.server_insert s create_response("Server '#{s.id}' has been added") } end def self.get_server_by_key id, key mongo = Devops::Db.connector key == "instance" ? mongo.server_by_instance_id(id) : mongo.server_by_chef_node_name(id) end def self.check_chef_node_name name, provider mongo = Devops::Db.connector mongo.server_by_chef_node_name name halt(400, "Server with name '#{name}' already exist") rescue RecordNotFound => e # server not found - OK s = provider.servers.detect {|s| s["name"] == name} halt(400, "#{provider.name} node with name '#{name}' already exist") unless s.nil? s = KnifeCommands.chef_node_list.detect {|n| n == name} halt(400, "Chef node with name '#{name}' already exist") unless s.nil? s = KnifeCommands.chef_client_list.detect {|c| c == name} halt(400, "Chef client with name '#{name}' already exist") unless s.nil? end end class ExpireHandler include ServerCommands def initialize server, logger @server = server @logger = logger end def call(job) @logger.info("Removing node '#{@server.chef_node_name}' form project '#{@server.project}' and env '#{@server.deploy_env}'") begin delete_server(@server, Devops::Db.connector, @logger) rescue => e logger.error "ExpiredHandler error: " + e.message end end end end end end