1 ## NB: This must work with Ruby 1.8!
3 # This providers permits the nova_admin_tenant_id paramter in neutron.conf
4 # to be set by providing a nova_admin_tenant_name to the Puppet module and
5 # using the Keystone REST API to translate the name into the corresponding
8 # This requires that tenant names be unique. If there are multiple matches
9 # for a given tenant name, this provider will raise an exception.
15 require 'puppet/util/inifile'
17 class KeystoneError < Puppet::Error
20 class KeystoneConnectionError < KeystoneError
23 class KeystoneAPIError < KeystoneError
26 # Provides common request handling semantics to the other methods in
30 # An HTTPRequest object
32 # A parsed URL (returned from URI.parse)
33 def handle_request(req, url)
35 # There is issue with ipv6 where address has to be in brackets, this causes the
36 # underlying ruby TCPSocket to fail. Net::HTTP.new will fail without brackets on
37 # joining the ipv6 address with :port or passing brackets to TCPSocket. It was
38 # found that if we use Net::HTTP.start with url.hostname the incriminated code
40 use_ssl = url.scheme == "https" ? true : false
41 http = Net::HTTP.start(url.hostname, url.port, {:use_ssl => use_ssl})
42 res = http.request(req)
45 raise KeystoneAPIError, "Received error response from Keystone server at #{url}: #{res.message}"
47 rescue Errno::ECONNREFUSED => detail
48 raise KeystoneConnectionError, "Failed to connect to Keystone server at #{url}: #{detail}"
49 rescue SocketError => detail
50 raise KeystoneConnectionError, "Failed to connect to Keystone server at #{url}: #{detail}"
56 # Authenticates to a Keystone server and obtains an authentication token.
57 # It returns a 2-element +[token, authinfo]+, where +token+ is a token
58 # suitable for passing to openstack apis in the +X-Auth-Token+ header, and
59 # +authinfo+ is the complete response from Keystone, including the service
60 # catalog (if available).
63 # Keystone endpoint URL. This function assumes API version
64 # 2.0 and an administrative endpoint, so this will typically look like
65 # +http://somehost:35357/v2.0+.
68 # Username for authentication.
71 # Password for authentication
79 def keystone_v2_authenticate(auth_url,
87 'passwordCredentials' => {
88 'username' => username,
89 'password' => password
94 post_args['auth']['tenantId'] = tenantId
98 post_args['auth']['tenantName'] = tenantName
101 url = URI.parse("#{auth_url}/tokens")
102 req = Net::HTTP::Post.new url.path
103 req['content-type'] = 'application/json'
104 req.body = post_args.to_json
106 res = handle_request(req, url)
107 data = JSON.parse res.body
108 return data['access']['token']['id']
111 # Queries a Keystone server to a list of all tenants.
114 # Keystone endpoint. See the notes for +auth_url+ in
115 # +keystone_v2_authenticate+.
118 # A Keystone token that will be passed in requests as the value of the
119 # +X-Auth-Token+ header.
121 def keystone_v2_tenants(auth_url,
124 url = URI.parse("#{auth_url}/tenants")
125 req = Net::HTTP::Get.new url.path
126 req['content-type'] = 'application/json'
127 req['x-auth-token'] = token
129 res = handle_request(req, url)
130 data = JSON.parse res.body
134 Puppet::Type.type(:nova_admin_tenant_id_setter).provide(:ruby) do
138 keystone_v2_authenticate(
139 @resource[:auth_url],
140 @resource[:auth_username],
141 @resource[:auth_password],
143 @resource[:auth_tenant_name])
146 def find_tenant_by_name (token)
147 tenants = keystone_v2_tenants(
148 @resource[:auth_url],
151 tenants.select{|tenant| tenant['name'] == @resource[:tenant_name]}
155 ini_file = Puppet::Util::IniConfig::File.new
156 ini_file.read("/etc/neutron/neutron.conf")
157 ini_file['DEFAULT'] && ini_file['DEFAULT']['nova_admin_tenant_id'] && ini_file['DEFAULT']['nova_admin_tenant_id'] == tenant_id
165 @tenant_id ||= get_tenant_id
168 # This looks for the tenant specified by the 'tenant_name' parameter to
169 # the resource and returns the corresponding UUID if there is a single
172 # Raises a KeystoneAPIError if:
174 # - There are multiple matches, or
175 # - There are zero matches
178 tenants = find_tenant_by_name(token)
180 if tenants.length == 1
181 return tenants[0]['id']
182 elsif tenants.length > 1
183 raise KeystoneAPIError, 'Found multiple matches for tenant name'
185 raise KeystoneAPIError, 'Unable to find matching tenant'
190 Puppet::Type.type(:neutron_config).new(
191 {:name => 'DEFAULT/nova_admin_tenant_id', :value => "#{tenant_id}"}