From 3a38aa6eda5a57299f20bf0cabb10442e1a2cc89 Mon Sep 17 00:00:00 2001 From: Martin Magr Date: Thu, 5 Jun 2014 14:24:47 +0200 Subject: [PATCH] Implement Keystone domain creation Keystone domain has to be created for Heat. This patch implements this via helper script [1] since we don't have support for Keystone v3 API in puppet-keystone yet. This implementation should be refactored as soon as we will have v3 API available in puppet-keystone. For more info please check [2]. [1] https://github.com/openstack/heat/blob/master/bin/heat-keystone-setup-domain [2] https://bugzilla.redhat.com/show_bug.cgi?id=1076172 --- lib/puppet/provider/heat_domain_id_setter/ruby.rb | 183 ++++++++++++++++++++++ lib/puppet/type/heat_config.rb | 4 + lib/puppet/type/heat_domain_id_setter.rb | 31 ++++ manifests/keystone/domain.pp | 73 +++++++++ 4 files changed, 291 insertions(+) create mode 100644 lib/puppet/provider/heat_domain_id_setter/ruby.rb create mode 100644 lib/puppet/type/heat_domain_id_setter.rb create mode 100644 manifests/keystone/domain.pp diff --git a/lib/puppet/provider/heat_domain_id_setter/ruby.rb b/lib/puppet/provider/heat_domain_id_setter/ruby.rb new file mode 100644 index 0000000..8862fe5 --- /dev/null +++ b/lib/puppet/provider/heat_domain_id_setter/ruby.rb @@ -0,0 +1,183 @@ +## NB: This must work with Ruby 1.8! + +# This provider permits the stack_user_domain parameter in heat.conf +# to be set by providing a domain_name to the Puppet module and +# using the Keystone REST API to translate the name into the corresponding +# UUID. +# +# This requires that tenant names be unique. If there are multiple matches +# for a given tenant name, this provider will raise an exception. + +require 'rubygems' +require 'net/http' +require 'json' + +class KeystoneError < Puppet::Error +end + +class KeystoneConnectionError < KeystoneError +end + +class KeystoneAPIError < KeystoneError +end + +# Provides common request handling semantics to the other methods in +# this module. +# +# +req+:: +# An HTTPRequest object +# +url+:: +# A parsed URL (returned from URI.parse) +def handle_request(req, url) + begin + res = Net::HTTP.start(url.host, url.port) {|http| + http.request(req) + } + + if res.code != '200' + raise KeystoneAPIError, "Received error response from Keystone server at #{url}: #{res.message}" + end + rescue Errno::ECONNREFUSED => detail + raise KeystoneConnectionError, "Failed to connect to Keystone server at #{url}: #{detail}" + rescue SocketError => detail + raise KeystoneConnectionError, "Failed to connect to Keystone server at #{url}: #{detail}" + end + + res +end + +# Authenticates to a Keystone server and obtains an authentication token. +# It returns a 2-element +[token, authinfo]+, where +token+ is a token +# suitable for passing to openstack apis in the +X-Auth-Token+ header, and +# +authinfo+ is the complete response from Keystone, including the service +# catalog (if available). +# +# +auth_url+:: +# Keystone endpoint URL. This function assumes API version +# 2.0 and an administrative endpoint, so this will typically look like +# +http://somehost:35357/v2.0+. +# +# +username+:: +# Username for authentication. +# +# +password+:: +# Password for authentication +# +# +tenantID+:: +# Tenant UUID +# +# +tenantName+:: +# Tenant name +# +def keystone_v2_authenticate(auth_url, + username, + password, + tenantId=nil, + tenantName=nil) + + post_args = { + 'auth' => { + 'passwordCredentials' => { + 'username' => username, + 'password' => password + }, + }} + + if tenantId + post_args['auth']['tenantId'] = tenantId + end + + if tenantName + post_args['auth']['tenantName'] = tenantName + end + + url = URI.parse("#{auth_url}/tokens") + req = Net::HTTP::Post.new url.path + req['content-type'] = 'application/json' + req.body = post_args.to_json + + res = handle_request(req, url) + data = JSON.parse res.body + return data['access']['token']['id'], data +end + +# Queries a Keystone server to a list of all tenants. +# +# +auth_url+:: +# Keystone endpoint. See the notes for +auth_url+ in +# +keystone_v2_authenticate+. +# +# +token+:: +# A Keystone token that will be passed in requests as the value of the +# +X-Auth-Token+ header. +# +def keystone_v3_domains(auth_url, + token) + + auth_url.sub!('v2.0', 'v3') + url = URI.parse("#{auth_url}/domains") + req = Net::HTTP::Get.new url.path + req['content-type'] = 'application/json' + req['x-auth-token'] = token + + res = handle_request(req, url) + data = JSON.parse res.body + data['domains'] +end + +Puppet::Type.type(:heat_domain_id_setter).provide(:ruby) do + def authenticate + token, authinfo = keystone_v2_authenticate( + @resource[:auth_url], + @resource[:auth_username], + @resource[:auth_password], + nil, + @resource[:auth_tenant_name]) + + return token + end + + def find_domain_by_name(token) + domains = keystone_v3_domains( + @resource[:auth_url], + token) + domains.select{|domain| domain['name'] == @resource[:domain_name]} + end + + def exists? + false + end + + def create + config + end + + # This looks for the domain specified by the 'domain_name' parameter to + # the resource and returns the corresponding UUID if there is a single + # match. + # + # Raises a KeystoneAPIError if: + # + # - There are multiple matches, or + # - There are zero matches + def get_domain_id + token = authenticate + domains = find_domain_by_name(token) + + if domains.length == 1 + return domains[0]['id'] + elsif domains.length > 1 + name = domains[0]['name'] + raise KeystoneAPIError, 'Found multiple matches for domain name "#{name}"' + else + raise KeystoneAPIError, 'Unable to find matching tenant' + end + end + + def config + Puppet::Type.type(:heat_config).new( + {:name => 'DEFAULT/stack_user_domain', :value => "#{get_domain_id}"} + ).create + end + +end diff --git a/lib/puppet/type/heat_config.rb b/lib/puppet/type/heat_config.rb index 3534e22..131cfad 100644 --- a/lib/puppet/type/heat_config.rb +++ b/lib/puppet/type/heat_config.rb @@ -16,4 +16,8 @@ Puppet::Type.newtype(:heat_config) do end end + def create + provider.create + end + end diff --git a/lib/puppet/type/heat_domain_id_setter.rb b/lib/puppet/type/heat_domain_id_setter.rb new file mode 100644 index 0000000..d6e1eee --- /dev/null +++ b/lib/puppet/type/heat_domain_id_setter.rb @@ -0,0 +1,31 @@ +Puppet::Type.newtype(:heat_domain_id_setter) do + + ensurable + + newparam(:name, :namevar => true) do + desc 'The name of the setting to update' + end + + newparam(:domain_name) do + desc 'The heat domain name' + end + + newparam(:auth_url) do + desc 'The Keystone endpoint URL' + defaultto 'http://localhost:35357/v2.0' + end + + newparam(:auth_username) do + desc 'Username with which to authenticate' + defaultto 'admin' + end + + newparam(:auth_password) do + desc 'Password with which to authenticate' + end + + newparam(:auth_tenant_name) do + desc 'Tenant name with which to authenticate' + defaultto 'admin' + end +end diff --git a/manifests/keystone/domain.pp b/manifests/keystone/domain.pp new file mode 100644 index 0000000..0ef4b6a --- /dev/null +++ b/manifests/keystone/domain.pp @@ -0,0 +1,73 @@ +# == Class: heat::keystone::domain +# +# Configures heat domain in Keystone. +# +# Note: Implementation is done by heat-keystone-setup-domain script temporarily +# because currently puppet-keystone does not support v3 API +# +# === Parameters +# +# [*auth_url*] +# Keystone auth url +# +# [*keystone_admin*] +# Keystone admin user +# +# [*keystone_password*] +# Keystone admin password +# +# [*keystone_tenant*] +# Keystone admin tenant name +# +# [*domain_name*] +# Heat domain name. Defaults to 'heat'. +# +# [*domain_admin*] +# Keystone domain admin user which will be created. Defaults to 'heat_admin'. +# +# [*domain_password*] +# Keystone domain admin user password. Defaults to 'changeme'. +# +class heat::keystone::domain ( + $auth_url = undef, + $keystone_admin = undef, + $keystone_password = undef, + $keystone_tenant = undef, + $domain_name = 'heat', + $domain_admin = 'heat_admin', + $domain_password = 'changeme', +) { + + include heat::params + + $cmd_evn = [ + "OS_USERNAME=${keystone_admin}", + "OS_PASSWORD=${keystone_password}", + "OS_AUTH_URL=${auth_url}", + "HEAT_DOMAIN=${domain_name}", + "HEAT_DOMAIN_ADMIN=${domain_admin}", + "HEAT_DOMAIN_PASSWORD=${domain_password}" + ] + exec { 'heat_domain_create': + path => '/usr/bin', + command => 'heat-keystone-setup-domain &>/dev/null', + environment => $cmd_evn, + require => Package['heat-common'], + } + + heat_domain_id_setter { 'heat_domain_id': + ensure => present, + domain_name => $domain_name, + auth_url => $auth_url, + auth_username => $keystone_admin, + auth_password => $keystone_password, + auth_tenant_name => $keystone_tenant, + require => Exec['heat_domain_create'], + } + + heat_config { + 'DEFAULT/stack_domain_admin': value => $domain_admin; + 'DEFAULT/stack_domain_admin_password': value => $domain_password; + } + +} -- 1.9.3