b36c19fa911925b061e424e705fa90c47b21f414
[snaps.git] / snaps / openstack / utils / keystone_utils.py
1 # Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
2 #                    and others.  All rights reserved.
3 #
4 # Licensed under the Apache License, Version 2.0 (the "License");
5 # you may not use this file except in compliance with the License.
6 # You may obtain a copy of the License at:
7 #
8 #     http://www.apache.org/licenses/LICENSE-2.0
9 #
10 # Unless required by applicable law or agreed to in writing, software
11 # distributed under the License is distributed on an "AS IS" BASIS,
12 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 # See the License for the specific language governing permissions and
14 # limitations under the License.
15 import logging
16
17 from keystoneclient.client import Client
18 from keystoneauth1.identity import v3, v2
19 from keystoneauth1 import session
20 import requests
21
22 from snaps.domain.project import Project, Domain
23 from snaps.domain.role import Role
24 from snaps.domain.user import User
25
26 logger = logging.getLogger('keystone_utils')
27
28 V2_VERSION_NUM = 2.0
29 V2_VERSION_STR = 'v' + str(V2_VERSION_NUM)
30
31
32 def get_session_auth(os_creds):
33     """
34     Return the session auth for keystone session
35     :param os_creds: the OpenStack credentials (OSCreds) object
36     :return: the auth
37     """
38     if os_creds.identity_api_version == 3:
39         auth = v3.Password(auth_url=os_creds.auth_url,
40                            username=os_creds.username,
41                            password=os_creds.password,
42                            project_name=os_creds.project_name,
43                            user_domain_id=os_creds.user_domain_id,
44                            user_domain_name=os_creds.user_domain_name,
45                            project_domain_id=os_creds.project_domain_id,
46                            project_domain_name=os_creds.project_domain_name)
47     else:
48         auth = v2.Password(auth_url=os_creds.auth_url,
49                            username=os_creds.username,
50                            password=os_creds.password,
51                            tenant_name=os_creds.project_name)
52     return auth
53
54
55 def keystone_session(os_creds):
56     """
57     Creates a keystone session used for authenticating OpenStack clients
58     :param os_creds: The connection credentials to the OpenStack API
59     :return: the client object
60     """
61     logger.debug('Retrieving Keystone Session')
62
63     auth = get_session_auth(os_creds)
64
65     req_session = None
66     if os_creds.proxy_settings:
67         req_session = requests.Session()
68         req_session.proxies = {
69             'http':
70                 os_creds.proxy_settings.host + ':' +
71                 os_creds.proxy_settings.port,
72             'https':
73                 os_creds.proxy_settings.https_host + ':' +
74                 os_creds.proxy_settings.https_port
75         }
76     return session.Session(auth=auth, session=req_session,
77                            verify=os_creds.cacert)
78
79
80 def keystone_client(os_creds):
81     """
82     Returns the keystone client
83     :param os_creds: the OpenStack credentials (OSCreds) object
84     :return: the client
85     """
86     return Client(
87         version=os_creds.identity_api_version,
88         session=keystone_session(os_creds),
89         interface=os_creds.interface,
90         region_name=os_creds.region_name)
91
92
93 def get_endpoint(os_creds, service_type, interface='public'):
94     """
95     Returns the endpoint of specific service
96     :param os_creds: the OpenStack credentials (OSCreds) object
97     :param service_type: the type of specific service
98     :param interface: the type of interface
99     :return: the endpoint url
100     """
101     auth = get_session_auth(os_creds)
102     key_session = keystone_session(os_creds)
103     return key_session.get_endpoint(
104         auth=auth, service_type=service_type, region_name=os_creds.region_name,
105         interface=interface)
106
107
108 def get_project(keystone=None, os_creds=None, project_settings=None,
109                 project_name=None):
110     """
111     Returns the first project where the project_settings is used for the query
112     if not None, else the project_name parameter is used for the query. If both
113     parameters are None, None is returned
114     :param keystone: the Keystone client
115     :param os_creds: the OpenStack credentials used to obtain the Keystone
116                      client if the keystone parameter is None
117     :param project_settings: a ProjectSettings object
118     :param project_name: the name to query
119     :return: the SNAPS-OO Project domain object or None
120     """
121     if not keystone:
122         if os_creds:
123             keystone = keystone_client(os_creds)
124         else:
125             raise KeystoneException(
126                 'Cannot lookup project without the proper credentials')
127
128     proj_filter = dict()
129
130     if project_name:
131         proj_filter['name'] = project_name
132     elif project_settings:
133         proj_filter['name'] = project_settings.name
134         proj_filter['description'] = project_settings.description
135         proj_filter['domain_name'] = project_settings.domain_name
136         proj_filter['enabled'] = project_settings.enabled
137     else:
138         return None
139
140     if keystone.version == V2_VERSION_STR:
141         projects = keystone.tenants.list()
142     else:
143         projects = keystone.projects.list(**proj_filter)
144
145     for project in projects:
146         if project.name == proj_filter['name']:
147             domain_id = None
148             if keystone.version != V2_VERSION_STR:
149                 domain_id = project.domain_id
150
151             return Project(name=project.name, project_id=project.id,
152                            domain_id=domain_id)
153
154
155 def create_project(keystone, project_settings):
156     """
157     Creates a project
158     :param keystone: the Keystone client
159     :param project_settings: the project configuration
160     :return: SNAPS-OO Project domain object
161     """
162     domain_id = None
163
164     if keystone.version == V2_VERSION_STR:
165         os_project = keystone.tenants.create(
166             project_settings.name, project_settings.description,
167             project_settings.enabled)
168     else:
169         os_domain = __get_os_domain_by_name(
170             keystone, project_settings.domain_name)
171         if not os_domain:
172             os_domain = project_settings.domain_name
173         os_project = keystone.projects.create(
174             project_settings.name, os_domain,
175             description=project_settings.description,
176             enabled=project_settings.enabled)
177         domain_id = os_project.domain_id
178
179     return Project(
180         name=os_project.name, project_id=os_project.id, domain_id=domain_id)
181
182
183 def delete_project(keystone, project):
184     """
185     Deletes a project
186     :param keystone: the Keystone clien
187     :param project: the SNAPS-OO Project domain object
188     """
189     if keystone.version == V2_VERSION_STR:
190         keystone.tenants.delete(project.id)
191     else:
192         keystone.projects.delete(project.id)
193
194
195 def __get_os_user(keystone, user):
196     """
197     Returns the OpenStack user object
198     :param keystone: the Keystone client object
199     :param user: the SNAPS-OO User domain object
200     :return:
201     """
202     return keystone.users.get(user.id)
203
204
205 def get_user(keystone, username, project_name=None):
206     """
207     Returns a user for a given name and optionally project
208     :param keystone: the keystone client
209     :param username: the username to lookup
210     :param project_name: the associated project (optional)
211     :return: a SNAPS-OO User domain object or None
212     """
213     project = None
214     if project_name:
215         project = get_project(keystone=keystone, project_name=project_name)
216
217     if project:
218         users = keystone.users.list(tenant_id=project.id)
219     else:
220         users = keystone.users.list()
221
222     for user in users:
223         if user.name == username:
224             return User(name=user.name, user_id=user.id)
225
226     return None
227
228
229 def create_user(keystone, user_settings):
230     """
231     Creates a user
232     :param keystone: the Keystone client
233     :param user_settings: the user configuration
234     :return: a SNAPS-OO User domain object
235     """
236     project = None
237     if user_settings.project_name:
238         project = get_project(keystone=keystone,
239                               project_name=user_settings.project_name)
240
241     if keystone.version == V2_VERSION_STR:
242         project_id = None
243         if project:
244             project_id = project.id
245         os_user = keystone.users.create(
246             name=user_settings.name, password=user_settings.password,
247             email=user_settings.email, tenant_id=project_id,
248             enabled=user_settings.enabled)
249     else:
250         os_domain = __get_os_domain_by_name(
251             keystone, user_settings.domain_name)
252         if not os_domain:
253             os_domain = user_settings.domain_name
254         os_user = keystone.users.create(
255             name=user_settings.name, password=user_settings.password,
256             email=user_settings.email, project=project,
257             domain=os_domain, enabled=user_settings.enabled)
258
259     for role_name, role_project in user_settings.roles.items():
260         os_role = get_role_by_name(keystone, role_name)
261         os_project = get_project(keystone=keystone, project_name=role_project)
262
263         if os_role and os_project:
264             existing_roles = get_roles_by_user(keystone, os_user, os_project)
265             found = False
266             for role in existing_roles:
267                 if role.id == os_role.id:
268                     found = True
269
270             if not found:
271                 grant_user_role_to_project(
272                     keystone=keystone, user=os_user, role=os_role,
273                     project=os_project)
274
275     if os_user:
276         return User(name=os_user.name, user_id=os_user.id)
277
278
279 def delete_user(keystone, user):
280     """
281     Deletes a user
282     :param keystone: the Keystone client
283     :param user: the SNAPS-OO User domain object
284     """
285     keystone.users.delete(user.id)
286
287
288 def get_role_by_name(keystone, name):
289     """
290     Returns an OpenStack role object of a given name or None if not exists
291     :param keystone: the keystone client
292     :param name: the role name
293     :return: the SNAPS-OO Role domain object
294     """
295     roles = keystone.roles.list()
296     for role in roles:
297         if role.name == name:
298             return Role(name=role.name, role_id=role.id)
299
300
301 def get_roles_by_user(keystone, user, project):
302     """
303     Returns a list of SNAPS-OO Role domain objects associated with a user
304     :param keystone: the keystone client
305     :param user: the OpenStack user object
306     :param project: the OpenStack project object (only required for v2)
307     :return: a list of SNAPS-OO Role domain objects
308     """
309     if keystone.version == V2_VERSION_STR:
310         os_user = __get_os_user(keystone, user)
311         roles = keystone.roles.roles_for_user(os_user, project)
312     else:
313         roles = keystone.roles.list(user=user, project=project)
314
315     out = list()
316     for role in roles:
317         out.append(Role(name=role.name, role_id=role.id))
318     return out
319
320
321 def get_role_by_id(keystone, role_id):
322     """
323     Returns an OpenStack role object of a given name or None if not exists
324     :param keystone: the keystone client
325     :param role_id: the role ID
326     :return: a SNAPS-OO Role domain object
327     """
328     role = keystone.roles.get(role_id)
329     return Role(name=role.name, role_id=role.id)
330
331
332 def create_role(keystone, name):
333     """
334     Creates an OpenStack role
335     :param keystone: the keystone client
336     :param name: the role name
337     :return: a SNAPS-OO Role domain object
338     """
339     role = keystone.roles.create(name)
340     return Role(name=role.name, role_id=role.id)
341
342
343 def delete_role(keystone, role):
344     """
345     Deletes an OpenStack role
346     :param keystone: the keystone client
347     :param role: the SNAPS-OO Role domain object to delete
348     :return:
349     """
350     keystone.roles.delete(role.id)
351
352
353 def grant_user_role_to_project(keystone, role, user, project):
354     """
355     Grants user and role to a project
356     :param keystone: the Keystone client
357     :param role: the SNAPS-OO Role domain object used to join a project/user
358     :param user: the user to add to the project (SNAPS-OO User Domain object
359     :param project: the project to which to add a user
360     :return:
361     """
362
363     os_role = get_role_by_id(keystone, role.id)
364     if keystone.version == V2_VERSION_STR:
365         keystone.roles.add_user_role(user, os_role, tenant=project)
366     else:
367         keystone.roles.grant(os_role, user=user, project=project)
368
369
370 def get_domain_by_id(keystone, domain_id):
371     """
372     Returns the first OpenStack domain with the given name else None
373     :param keystone: the Keystone client
374     :param domain_id: the domain ID to retrieve
375     :return: the SNAPS-OO Domain domain object
376     """
377     domain = keystone.domains.get(domain_id)
378     if domain:
379         return Domain(name=domain.name, domain_id=domain.id)
380
381
382 def __get_os_domain_by_name(keystone, domain_name):
383     """
384     Returns the first OpenStack domain with the given name else None
385     :param keystone: the Keystone client
386     :param domain_name: the domain name to lookup
387     :return: the OpenStack domain object
388     """
389     domains = keystone.domains.list(name=domain_name)
390     for domain in domains:
391         if domain.name == domain_name:
392             return domain
393
394
395 class KeystoneException(Exception):
396     """
397     Exception when calls to the Keystone client cannot be served properly
398     """