require "chef/json_compat" require "providers/abstract_provider_connector" require "providers/openstack/openstack_provider_account" require_relative "openstack_accounts_factory" module Provider # Provider for 'openstack' class OpenstackConnector < AbstractProviderConnector PROVIDER = "openstack" def initialize config self.connection_options = { :provider => PROVIDER, :openstack_username => config[:openstack_username], :openstack_api_key => config[:openstack_api_key], :openstack_auth_url => config[:openstack_auth_url], :openstack_tenant => config[:openstack_tenant] } self.run_list = config[:openstack_integration_run_list] || [] end # Returns 'true' if all parameters defined def configured? o = self.connection_options super and !(empty_param?(o[:openstack_username]) or empty_param?(o[:openstack_api_key]) or empty_param?(o[:openstack_auth_url]) or empty_param?(o[:openstack_tenant])) end def name PROVIDER end def security_groups filters={} convert_groups(compute.list_security_groups.body["security_groups"]) end def flavors self.compute.list_flavors_detail.body["flavors"].map do |f| { "id" => f["name"], "v_cpus" => f["vcpus"], "ram" => f["ram"], "disk" => f["disk"] } end end def images filters self.compute.list_images_detail.body["images"].select{|i| filters.include?(i["id"]) and i["status"] == "ACTIVE"}.map do |i| { "id" => i["id"], "name" => i["name"], "status" => i["status"] } end end def networks_detail filters={} net = self.network(filters) subnets = net.list_subnets.body["subnets"].select{|s| net.current_tenant["id"] == s["tenant_id"]} net.list_networks.body["networks"].select{|n| n["router:external"] == false and n["status"] == "ACTIVE" and net.current_tenant["id"] == n["tenant_id"]}.map{|n| sn = subnets.detect{|s| n["subnets"][0] == s["id"]} { "cidr" => sn["cidr"], "name" => n["name"], "id" => n["id"] } } end def networks filters={} net = self.network net.list_networks.body["networks"].select{|n| n["router:external"] == false and n["status"] == "ACTIVE" and net.current_tenant["id"] == n["tenant_id"]}.map{|n| { "name" => n["name"], "id" => n["id"] } } end def servers list = self.compute.list_servers_detail.body["servers"] list.map do |s| o = {"state" => s["status"], "name" => s["name"], "image" => s["image"]["id"], "flavor" => s["flavor"]["name"], "keypair" => s["key_name"], "instance_id" => s["id"]} s["addresses"].each_value do |a| a.each do |addr| o["private_ip"] = addr["addr"] if addr["OS-EXT-IPS:type"] == "fixed" end end o end end def create_server s, image, flavor, subnets, groups, out, options={} out << "Creating server for project '#{s.project} - #{s.environment}'\n" if s.name.nil? out << "Generate new instance name: " out << s.name = create_default_server_name(s) out << "\n" end networks = self.networks.select{|n| subnets.include?(n["name"])} buf = subnets - networks.map{|n| n["name"]} unless buf.empty? out << "No networks with names '#{buf.join("', '")}' found" return false end flavor = self.compute.list_flavors_detail.body["flavors"].detect{|f| f["name"] == flavor}["id"] out << "Creating server with name '#{s.name}', image '#{image}', flavor '#{flavor}', key '#{s.key}' and networks '#{networks.map{|n| n["name"]}.join("', '")}'...\n\n" compute = self.compute begin o_server = compute.create_server(s.name, image, flavor, "nics" => networks.map{|n| {"net_id" => n["id"]}}, "security_groups" => groups, "key_name" => s.key) rescue Excon::Errors::BadRequest => e response = ::Chef::JSONCompat.from_json(e.response.body) if response['badRequest']['code'] == 400 if response['badRequest']['message'] =~ /Invalid flavorRef/ out << "\nERROR: Bad request (400): Invalid flavor id specified: #{flavor}" elsif response['badRequest']['message'] =~ /Invalid imageRef/ out << "\nERROR: Bad request (400): Invalid image specified: #{image}" else out << "\nERROR: Bad request (400): #{response['badRequest']['message']}" end out << "\n" return false else out << "\nERROR: Unknown server error (#{response['badRequest']['code']}): #{response['badRequest']['message']}" out << "\n" return false end rescue Excon::Errors::InternalServerError => ise out << "\nError: openstack internal server error " + ise.message out << "\n" return false rescue => e2 out << "\nError: Unknown error: " + e2.message out << "\n" return false end sbody = o_server.body s.id = sbody["server"]["id"] return true end def waiting_server server, out details, status = nil, nil until status == "ACTIVE" sleep(5) details = compute.get_server_details(s.id).body status = details["server"]["status"].upcase if status == "ERROR" out << " error\nServer returns status 'ERROR'" out << details["server"] return false end out << "." out.flush end network = networks[0]["name"] s.private_ip = details["server"]["addresses"][network][0]["addr"] out << " Done\n\n" out.flush true end def delete_server s r = self.compute.delete_server(s.id) return r.status == 204 ? "Server with id '#{s.id}' terminated" : r.body end def pause_server s begin self.compute.pause_server s.id rescue Excon::Errors::Conflict => e return "pause" end return nil end def unpause_server s begin self.compute.unpause_server s.id rescue Excon::Errors::Conflict => e return "unpause" end return nil end def compute @compute ||= connection_compute(connection_options) end def network connection_network(self.connection_options) end def create_stack(stack, out) begin out << "Creating stack for project '#{stack.project}' and environment '#{stack.environment}'...\n" stack.name = create_default_stack_name(stack) unless stack.name out << "Stack name: #{stack.name}\n" out << "Stack template: #{stack.stack_template}\n" out << "Stack parameters: #{stack.parameters}\n" out.flush response = orchestration.create_stack( stack_name: stack.name, template: stack.stack_template_model.template_body, parameters: stack.parameters || {} ) stack.id = response[:body]['stack']['id'] out << "Stack id: #{stack.id}\n" out.flush rescue Excon::Errors::Conflict => e raise ProviderErrors::NameConflict rescue Excon::Errors::BadRequest => br response = ::Chef::JSONCompat.from_json(br.response.body) if response['code'] == 400 out << "\nERROR: Bad request (400): #{response['explanation']}" out << "\n" raise InvalidRecord.new(response['explanation']) else out << "\nERROR: Unknown server error (#{response['code']}): #{response['explanation']}" out << "\n" raise InvalidRecord.new(response['explanation']) end end end def validate_stack_template template r = orchestration.validate_template({'template' => template}) pp r.body end def delete_stack(stack) begin orchestration.delete_stack(Fog::Orchestration::OpenStack::Stack.new({'id' => stack.id, 'stack_name' => stack.name})) rescue Fog::Compute::OpenStack::NotFound puts 'already deleted' end end def stack_details(stack) details = orchestration.show_stack_details(stack.name, stack.id).body['stack'] { stack_status: details[:stack_status] } end def stack_resources(stack) orchestration.list_resources(Fog::Orchestration::OpenStack::Stack.new({'id' => stack.id, 'stack_name' => stack.name})).body['resources'] end def stack_resource(stack, resource_id) fog_stack = orchestration.stacks.get(stack.name, stack.id) physical_id = fog_stack(stack).resources.get(resource_id).physical_resource_id compute.servers.get(physical_id) end def stack_servers(stack) stack_resources(stack).map do |r| server = compute.servers.get(r['physical_resource_id']) net = server.addresses.first ip = net.last.first['addr'] { 'name' => server.name, 'id' => server.id, 'key_name' => server.key_name, 'private_ip' => ip, 'public_ip' => ip } end end private def convert_groups list res = {} list.map do |g| res[g["name"]] = { "description" => g["description"] } rules = [] g["rules"].each do |r| rules.push({ "protocol" => r["ip_protocol"], "from" => r["from_port"], "to" => r["to_port"], "cidr" => r["ip_range"]["cidr"] }) end res[g["name"]]["rules"] = rules end res end def orchestration @orchestration ||= Fog::Orchestration.new(connection_options) end end end