275 lines
8.9 KiB
Ruby
275 lines
8.9 KiB
Ruby
require "chef/json_compat"
|
|
require "providers/base_provider"
|
|
|
|
module Provider
|
|
# Provider for 'openstack'
|
|
class Openstack < BaseProvider
|
|
|
|
PROVIDER = "openstack"
|
|
|
|
def initialize config
|
|
self.certificate_path = config[:openstack_certificate]
|
|
self.ssh_key = config[:openstack_ssh_key]
|
|
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 groups filter=nil
|
|
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
|
|
net = self.network
|
|
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
|
|
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.deploy_env}'\n"
|
|
if s.chef_node_name.nil?
|
|
out << "Generate new instance name: "
|
|
out << s.chef_node_name = create_default_chef_node_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.chef_node_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.chef_node_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"]
|
|
|
|
out << "\nWaiting for server..."
|
|
|
|
details, status = nil, nil
|
|
until status == "ACTIVE"
|
|
sleep(1)
|
|
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
|
|
end
|
|
network = networks[0]["name"]
|
|
s.private_ip = details["server"]["addresses"][network][0]["addr"]
|
|
out << " done\n\n"
|
|
out << s.info
|
|
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.deploy_env}'...\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.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 delete_stack(stack)
|
|
orchestration.delete_stack(Fog::Orchestration::OpenStack::Stack.new({'id' => stack.id, 'stack_name' => stack.name}))
|
|
end
|
|
|
|
def stack_details(stack)
|
|
orchestration.show_stack_details(stack.name, stack.id).body['stack']
|
|
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)
|
|
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{|r| compute.servers.get(r['physical_resource_id'])}
|
|
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
|
|
@connection ||= Fog::Orchestration.new(connection_options)
|
|
end
|
|
|
|
end
|
|
end
|