fbef6990f3cc46e3562b6a0065f9f331ea046968
[mirror/dsa-puppet.git] / 3rdparty / modules / neutron / lib / puppet / provider / nova_admin_tenant_id_setter / ini_setting.rb
1 ## NB: This must work with Ruby 1.8!
2
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
6 # UUID.
7 #
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.
10
11 require 'rubygems'
12 require 'net/http'
13 require 'net/https'
14 require 'json'
15 require 'puppet/util/inifile'
16
17 class KeystoneError < Puppet::Error
18 end
19
20 class KeystoneConnectionError < KeystoneError
21 end
22
23 class KeystoneAPIError < KeystoneError
24 end
25
26 # Provides common request handling semantics to the other methods in
27 # this module.
28 #
29 # +req+::
30 #   An HTTPRequest object
31 # +url+::
32 #   A parsed URL (returned from URI.parse)
33 def handle_request(req, url)
34     begin
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
39         # won't be hit.
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)
43
44         if res.code != '200'
45             raise KeystoneAPIError, "Received error response from Keystone server at #{url}: #{res.message}"
46         end
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}"
51     end
52
53     res
54 end
55
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).
61 #
62 # +auth_url+::
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+.
66 #
67 # +username+::
68 #   Username for authentication.
69 #
70 # +password+::
71 #   Password for authentication
72 #
73 # +tenantID+::
74 #   Tenant UUID
75 #
76 # +tenantName+::
77 #   Tenant name
78 #
79 def keystone_v2_authenticate(auth_url,
80                              username,
81                              password,
82                              tenantId=nil,
83                              tenantName=nil)
84
85     post_args = {
86         'auth' => {
87             'passwordCredentials' => {
88                 'username' => username,
89                 'password' => password
90             },
91         }}
92
93     if tenantId
94         post_args['auth']['tenantId'] = tenantId
95     end
96
97     if tenantName
98         post_args['auth']['tenantName'] = tenantName
99     end
100
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
105
106     res = handle_request(req, url)
107     data = JSON.parse res.body
108     return data['access']['token']['id']
109 end
110
111 # Queries a Keystone server to a list of all tenants.
112 #
113 # +auth_url+::
114 #   Keystone endpoint.  See the notes for +auth_url+ in
115 #   +keystone_v2_authenticate+.
116 #
117 # +token+::
118 #   A Keystone token that will be passed in requests as the value of the
119 #   +X-Auth-Token+ header.
120 #
121 def keystone_v2_tenants(auth_url,
122                         token)
123
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
128
129     res = handle_request(req, url)
130     data = JSON.parse res.body
131     data['tenants']
132 end
133
134 Puppet::Type.type(:nova_admin_tenant_id_setter).provide(:ruby) do
135     @tenant_id = nil
136
137     def authenticate
138         keystone_v2_authenticate(
139           @resource[:auth_url],
140           @resource[:auth_username],
141           @resource[:auth_password],
142           nil,
143           @resource[:auth_tenant_name])
144     end
145
146     def find_tenant_by_name (token)
147         tenants  = keystone_v2_tenants(
148             @resource[:auth_url],
149             token)
150
151         tenants.select{|tenant| tenant['name'] == @resource[:tenant_name]}
152     end
153
154     def exists?
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
158     end
159
160     def create
161         config
162     end
163
164     def tenant_id
165       @tenant_id ||= get_tenant_id
166     end
167
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
170     # match.
171     #
172     # Raises a KeystoneAPIError if:
173     #
174     # - There are multiple matches, or
175     # - There are zero matches
176     def get_tenant_id
177         token = authenticate
178         tenants = find_tenant_by_name(token)
179
180         if tenants.length == 1
181             return tenants[0]['id']
182         elsif tenants.length > 1
183             raise KeystoneAPIError, 'Found multiple matches for tenant name'
184         else
185             raise KeystoneAPIError, 'Unable to find matching tenant'
186         end
187     end
188
189     def config
190         Puppet::Type.type(:neutron_config).new(
191             {:name => 'DEFAULT/nova_admin_tenant_id', :value => "#{tenant_id}"}
192         ).create
193     end
194
195 end
196