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