Closing keystone sessions after done with them.
[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 import keystoneauth1
18 from keystoneclient.client import Client
19 from keystoneauth1.identity import v3, v2
20 from keystoneauth1 import session
21 import requests
22 from keystoneclient.exceptions import NotFound
23
24 from snaps.domain.project import Project, Domain
25 from snaps.domain.role import Role
26 from snaps.domain.user import User
27
28 logger = logging.getLogger('keystone_utils')
29
30 V2_VERSION_NUM = 2.0
31 V2_VERSION_STR = 'v' + str(V2_VERSION_NUM)
32
33
34 def get_session_auth(os_creds):
35     """
36     Return the session auth for keystone session
37     :param os_creds: the OpenStack credentials (OSCreds) object
38     :return: the auth
39     """
40     if os_creds.identity_api_version == 3:
41         auth = v3.Password(auth_url=os_creds.auth_url,
42                            username=os_creds.username,
43                            password=os_creds.password,
44                            project_name=os_creds.project_name,
45                            user_domain_id=os_creds.user_domain_id,
46                            user_domain_name=os_creds.user_domain_name,
47                            project_domain_id=os_creds.project_domain_id,
48                            project_domain_name=os_creds.project_domain_name)
49     else:
50         auth = v2.Password(auth_url=os_creds.auth_url,
51                            username=os_creds.username,
52                            password=os_creds.password,
53                            tenant_name=os_creds.project_name)
54     return auth
55
56
57 def keystone_session(os_creds):
58     """
59     Creates a keystone session used for authenticating OpenStack clients
60     :param os_creds: The connection credentials to the OpenStack API
61     :return: the client object
62     """
63     logger.debug('Retrieving Keystone Session')
64
65     auth = get_session_auth(os_creds)
66
67     req_session = None
68     if os_creds.proxy_settings:
69         req_session = requests.Session()
70         req_session.proxies = {
71             'http':
72                 os_creds.proxy_settings.host + ':' +
73                 os_creds.proxy_settings.port,
74             'https':
75                 os_creds.proxy_settings.https_host + ':' +
76                 os_creds.proxy_settings.https_port
77         }
78     return session.Session(auth=auth, session=req_session,
79                            verify=os_creds.cacert)
80
81
82 def close_session(session):
83     """
84     Closes a keystone session
85     :param session: a session.Session object
86     """
87     if isinstance(session, keystoneauth1.session.Session):
88         session.session.close()
89
90
91 def keystone_client(os_creds, session=None):
92     """
93     Returns the keystone client
94     :param os_creds: the OpenStack credentials (OSCreds) object
95     :param session: the keystone session object (optional)
96     :return: the client
97     """
98
99     if not session:
100         session = keystone_session(os_creds)
101
102     return Client(
103         version=os_creds.identity_api_version,
104         session=session,
105         interface=os_creds.interface,
106         region_name=os_creds.region_name)
107
108
109 def get_endpoint(os_creds, service_type, interface='public'):
110     """
111     Returns the endpoint of specific service
112     :param os_creds: the OpenStack credentials (OSCreds) object
113     :param service_type: the type of specific service
114     :param interface: the type of interface
115     :return: the endpoint url
116     """
117     auth = get_session_auth(os_creds)
118     key_session = keystone_session(os_creds)
119     return key_session.get_endpoint(
120         auth=auth, service_type=service_type, region_name=os_creds.region_name,
121         interface=interface)
122
123
124 def get_project(keystone=None, project_settings=None, project_name=None):
125     """
126     Returns the first project where the project_settings is used for the query
127     if not None, else the project_name parameter is used for the query. If both
128     parameters are None, None is returned
129     :param keystone: the Keystone client
130     :param project_settings: a ProjectConfig object
131     :param project_name: the name to query
132     :return: the SNAPS-OO Project domain object or None
133     """
134     proj_filter = dict()
135
136     if project_name:
137         proj_filter['name'] = project_name
138     elif project_settings:
139         proj_filter['name'] = project_settings.name
140         proj_filter['description'] = project_settings.description
141         proj_filter['domain_name'] = project_settings.domain_name
142         proj_filter['enabled'] = project_settings.enabled
143     else:
144         return None
145
146     if keystone.version == V2_VERSION_STR:
147         projects = keystone.tenants.list()
148     else:
149         projects = keystone.projects.list(**proj_filter)
150
151     for project in projects:
152         if project.name == proj_filter['name']:
153             domain_id = None
154             if keystone.version != V2_VERSION_STR:
155                 domain_id = project.domain_id
156
157             return Project(name=project.name, project_id=project.id,
158                            domain_id=domain_id)
159
160
161 def get_project_by_id(keystone, proj_id):
162     """
163     Returns the first project where the project_settings is used for the query
164     if not None, else the project_name parameter is used for the query. If both
165     parameters are None, None is returned
166     :param keystone: the Keystone client
167     :param proj_id: the project ID
168     """
169     if proj_id and len(proj_id) > 0:
170         try:
171             os_proj = keystone.projects.get(proj_id)
172             if os_proj:
173                 return Project(name=os_proj.name, project_id=os_proj.id,
174                                domain_id=os_proj)
175         except NotFound:
176             pass
177         except KeyError:
178             pass
179
180
181 def create_project(keystone, project_settings):
182     """
183     Creates a project
184     :param keystone: the Keystone client
185     :param project_settings: the project configuration
186     :return: SNAPS-OO Project domain object
187     """
188     domain_id = None
189
190     if keystone.version == V2_VERSION_STR:
191         os_project = keystone.tenants.create(
192             project_settings.name, project_settings.description,
193             project_settings.enabled)
194     else:
195         os_domain = __get_os_domain_by_name(
196             keystone, project_settings.domain_name)
197         if not os_domain:
198             os_domain = project_settings.domain_name
199         os_project = keystone.projects.create(
200             project_settings.name, os_domain,
201             description=project_settings.description,
202             enabled=project_settings.enabled)
203         domain_id = os_project.domain_id
204
205     logger.info('Created project with name - %s', project_settings.name)
206     return Project(
207         name=os_project.name, project_id=os_project.id, domain_id=domain_id)
208
209
210 def delete_project(keystone, project):
211     """
212     Deletes a project
213     :param keystone: the Keystone clien
214     :param project: the SNAPS-OO Project domain object
215     """
216     logger.info('Deleting project with name - %s', project.name)
217     if keystone.version == V2_VERSION_STR:
218         keystone.tenants.delete(project.id)
219     else:
220         keystone.projects.delete(project.id)
221
222
223 def __get_os_user(keystone, user):
224     """
225     Returns the OpenStack user object
226     :param keystone: the Keystone client object
227     :param user: the SNAPS-OO User domain object
228     :return:
229     """
230     return keystone.users.get(user.id)
231
232
233 def get_user(keystone, username, project_name=None):
234     """
235     Returns a user for a given name and optionally project
236     :param keystone: the keystone client
237     :param username: the username to lookup
238     :param project_name: the associated project (optional)
239     :return: a SNAPS-OO User domain object or None
240     """
241     project = None
242     if project_name:
243         project = get_project(keystone=keystone, project_name=project_name)
244
245     if project:
246         users = keystone.users.list(tenant_id=project.id)
247     else:
248         users = keystone.users.list()
249
250     for user in users:
251         if user.name == username:
252             return User(name=user.name, user_id=user.id)
253
254     return None
255
256
257 def create_user(keystone, user_settings):
258     """
259     Creates a user
260     :param keystone: the Keystone client
261     :param user_settings: the user configuration
262     :return: a SNAPS-OO User domain object
263     """
264     project = None
265     if user_settings.project_name:
266         project = get_project(
267             keystone=keystone, project_name=user_settings.project_name)
268
269     if keystone.version == V2_VERSION_STR:
270         project_id = None
271         if project:
272             project_id = project.id
273         os_user = keystone.users.create(
274             name=user_settings.name, password=user_settings.password,
275             email=user_settings.email, tenant_id=project_id,
276             enabled=user_settings.enabled)
277     else:
278         os_domain = __get_os_domain_by_name(
279             keystone, user_settings.domain_name)
280         if not os_domain:
281             os_domain = user_settings.domain_name
282         os_user = keystone.users.create(
283             name=user_settings.name, password=user_settings.password,
284             email=user_settings.email, project=project,
285             domain=os_domain, enabled=user_settings.enabled)
286
287     for role_name, role_project in user_settings.roles.items():
288         os_role = get_role_by_name(keystone, role_name)
289         os_project = get_project(keystone=keystone, project_name=role_project)
290
291         if os_role and os_project:
292             existing_roles = get_roles_by_user(keystone, os_user, os_project)
293             found = False
294             for role in existing_roles:
295                 if role.id == os_role.id:
296                     found = True
297
298             if not found:
299                 grant_user_role_to_project(
300                     keystone=keystone, user=os_user, role=os_role,
301                     project=os_project)
302
303     if os_user:
304         logger.info('Created user with name - %s', os_user.name)
305         return User(name=os_user.name, user_id=os_user.id)
306
307
308 def delete_user(keystone, user):
309     """
310     Deletes a user
311     :param keystone: the Keystone client
312     :param user: the SNAPS-OO User domain object
313     """
314     logger.info('Deleting user with name - %s', user.name)
315     keystone.users.delete(user.id)
316
317
318 def get_role_by_name(keystone, name):
319     """
320     Returns an OpenStack role object of a given name or None if not exists
321     :param keystone: the keystone client
322     :param name: the role name
323     :return: the SNAPS-OO Role domain object
324     """
325     roles = keystone.roles.list()
326     for role in roles:
327         if role.name == name:
328             return Role(name=role.name, role_id=role.id)
329
330
331 def get_roles_by_user(keystone, user, project):
332     """
333     Returns a list of SNAPS-OO Role domain objects associated with a user
334     :param keystone: the keystone client
335     :param user: the OpenStack user object
336     :param project: the OpenStack project object (only required for v2)
337     :return: a list of SNAPS-OO Role domain objects
338     """
339     if keystone.version == V2_VERSION_STR:
340         os_user = __get_os_user(keystone, user)
341         roles = keystone.roles.roles_for_user(os_user, project)
342     else:
343         roles = keystone.roles.list(user=user, project=project)
344
345     out = list()
346     for role in roles:
347         out.append(Role(name=role.name, role_id=role.id))
348     return out
349
350
351 def get_role_by_id(keystone, role_id):
352     """
353     Returns an OpenStack role object of a given name or None if not exists
354     :param keystone: the keystone client
355     :param role_id: the role ID
356     :return: a SNAPS-OO Role domain object
357     """
358     role = keystone.roles.get(role_id)
359     return Role(name=role.name, role_id=role.id)
360
361
362 def create_role(keystone, name):
363     """
364     Creates an OpenStack role
365     :param keystone: the keystone client
366     :param name: the role name
367     :return: a SNAPS-OO Role domain object
368     """
369     role = keystone.roles.create(name)
370     logger.info('Created role with name - %s', role.name)
371     return Role(name=role.name, role_id=role.id)
372
373
374 def delete_role(keystone, role):
375     """
376     Deletes an OpenStack role
377     :param keystone: the keystone client
378     :param role: the SNAPS-OO Role domain object to delete
379     :return:
380     """
381     logger.info('Deleting role with name - %s', role.name)
382     keystone.roles.delete(role.id)
383
384
385 def grant_user_role_to_project(keystone, role, user, project):
386     """
387     Grants user and role to a project
388     :param keystone: the Keystone client
389     :param role: the SNAPS-OO Role domain object used to join a project/user
390     :param user: the user to add to the project (SNAPS-OO User Domain object
391     :param project: the project to which to add a user
392     :return:
393     """
394
395     os_role = get_role_by_id(keystone, role.id)
396     logger.info('Granting role %s to project %s', role.name, project.name)
397     if keystone.version == V2_VERSION_STR:
398         keystone.roles.add_user_role(user, os_role, tenant=project)
399     else:
400         keystone.roles.grant(os_role, user=user, project=project)
401
402
403 def get_domain_by_id(keystone, domain_id):
404     """
405     Returns the first OpenStack domain with the given name else None
406     :param keystone: the Keystone client
407     :param domain_id: the domain ID to retrieve
408     :return: the SNAPS-OO Domain domain object
409     """
410     if keystone.version != V2_VERSION_STR:
411         domain = keystone.domains.get(domain_id)
412         if domain:
413             return Domain(name=domain.name, domain_id=domain.id)
414
415
416 def __get_os_domain_by_name(keystone, domain_name):
417     """
418     Returns the first OpenStack domain with the given name else None
419     :param keystone: the Keystone client
420     :param domain_name: the domain name to lookup
421     :return: the OpenStack domain object
422     """
423     domains = keystone.domains.list(name=domain_name)
424     for domain in domains:
425         if domain.name == domain_name:
426             return domain
427
428
429 class KeystoneException(Exception):
430     """
431     Exception when calls to the Keystone client cannot be served properly
432     """