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