Added method to OpenStackHeatStack to return OpenStackFlavor objects.
[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     logger.info('Created project with name - %s', project_settings.name)
180     return Project(
181         name=os_project.name, project_id=os_project.id, domain_id=domain_id)
182
183
184 def delete_project(keystone, project):
185     """
186     Deletes a project
187     :param keystone: the Keystone clien
188     :param project: the SNAPS-OO Project domain object
189     """
190     logger.info('Deleting project with name - %s', project.name)
191     if keystone.version == V2_VERSION_STR:
192         keystone.tenants.delete(project.id)
193     else:
194         keystone.projects.delete(project.id)
195
196
197 def __get_os_user(keystone, user):
198     """
199     Returns the OpenStack user object
200     :param keystone: the Keystone client object
201     :param user: the SNAPS-OO User domain object
202     :return:
203     """
204     return keystone.users.get(user.id)
205
206
207 def get_user(keystone, username, project_name=None):
208     """
209     Returns a user for a given name and optionally project
210     :param keystone: the keystone client
211     :param username: the username to lookup
212     :param project_name: the associated project (optional)
213     :return: a SNAPS-OO User domain object or None
214     """
215     project = None
216     if project_name:
217         project = get_project(keystone=keystone, project_name=project_name)
218
219     if project:
220         users = keystone.users.list(tenant_id=project.id)
221     else:
222         users = keystone.users.list()
223
224     for user in users:
225         if user.name == username:
226             return User(name=user.name, user_id=user.id)
227
228     return None
229
230
231 def create_user(keystone, user_settings):
232     """
233     Creates a user
234     :param keystone: the Keystone client
235     :param user_settings: the user configuration
236     :return: a SNAPS-OO User domain object
237     """
238     project = None
239     if user_settings.project_name:
240         project = get_project(keystone=keystone,
241                               project_name=user_settings.project_name)
242
243     if keystone.version == V2_VERSION_STR:
244         project_id = None
245         if project:
246             project_id = project.id
247         os_user = keystone.users.create(
248             name=user_settings.name, password=user_settings.password,
249             email=user_settings.email, tenant_id=project_id,
250             enabled=user_settings.enabled)
251     else:
252         os_domain = __get_os_domain_by_name(
253             keystone, user_settings.domain_name)
254         if not os_domain:
255             os_domain = user_settings.domain_name
256         os_user = keystone.users.create(
257             name=user_settings.name, password=user_settings.password,
258             email=user_settings.email, project=project,
259             domain=os_domain, enabled=user_settings.enabled)
260
261     for role_name, role_project in user_settings.roles.items():
262         os_role = get_role_by_name(keystone, role_name)
263         os_project = get_project(keystone=keystone, project_name=role_project)
264
265         if os_role and os_project:
266             existing_roles = get_roles_by_user(keystone, os_user, os_project)
267             found = False
268             for role in existing_roles:
269                 if role.id == os_role.id:
270                     found = True
271
272             if not found:
273                 grant_user_role_to_project(
274                     keystone=keystone, user=os_user, role=os_role,
275                     project=os_project)
276
277     if os_user:
278         logger.info('Created user with name - %s', os_user.name)
279         return User(name=os_user.name, user_id=os_user.id)
280
281
282 def delete_user(keystone, user):
283     """
284     Deletes a user
285     :param keystone: the Keystone client
286     :param user: the SNAPS-OO User domain object
287     """
288     logger.info('Deleting user with name - %s', user.name)
289     keystone.users.delete(user.id)
290
291
292 def get_role_by_name(keystone, name):
293     """
294     Returns an OpenStack role object of a given name or None if not exists
295     :param keystone: the keystone client
296     :param name: the role name
297     :return: the SNAPS-OO Role domain object
298     """
299     roles = keystone.roles.list()
300     for role in roles:
301         if role.name == name:
302             return Role(name=role.name, role_id=role.id)
303
304
305 def get_roles_by_user(keystone, user, project):
306     """
307     Returns a list of SNAPS-OO Role domain objects associated with a user
308     :param keystone: the keystone client
309     :param user: the OpenStack user object
310     :param project: the OpenStack project object (only required for v2)
311     :return: a list of SNAPS-OO Role domain objects
312     """
313     if keystone.version == V2_VERSION_STR:
314         os_user = __get_os_user(keystone, user)
315         roles = keystone.roles.roles_for_user(os_user, project)
316     else:
317         roles = keystone.roles.list(user=user, project=project)
318
319     out = list()
320     for role in roles:
321         out.append(Role(name=role.name, role_id=role.id))
322     return out
323
324
325 def get_role_by_id(keystone, role_id):
326     """
327     Returns an OpenStack role object of a given name or None if not exists
328     :param keystone: the keystone client
329     :param role_id: the role ID
330     :return: a SNAPS-OO Role domain object
331     """
332     role = keystone.roles.get(role_id)
333     return Role(name=role.name, role_id=role.id)
334
335
336 def create_role(keystone, name):
337     """
338     Creates an OpenStack role
339     :param keystone: the keystone client
340     :param name: the role name
341     :return: a SNAPS-OO Role domain object
342     """
343     role = keystone.roles.create(name)
344     logger.info('Created role with name - %s', role.name)
345     return Role(name=role.name, role_id=role.id)
346
347
348 def delete_role(keystone, role):
349     """
350     Deletes an OpenStack role
351     :param keystone: the keystone client
352     :param role: the SNAPS-OO Role domain object to delete
353     :return:
354     """
355     logger.info('Deleting role with name - %s', role.name)
356     keystone.roles.delete(role.id)
357
358
359 def grant_user_role_to_project(keystone, role, user, project):
360     """
361     Grants user and role to a project
362     :param keystone: the Keystone client
363     :param role: the SNAPS-OO Role domain object used to join a project/user
364     :param user: the user to add to the project (SNAPS-OO User Domain object
365     :param project: the project to which to add a user
366     :return:
367     """
368
369     os_role = get_role_by_id(keystone, role.id)
370     logger.info('Granting role %s to project %s', role.name, project.name)
371     if keystone.version == V2_VERSION_STR:
372         keystone.roles.add_user_role(user, os_role, tenant=project)
373     else:
374         keystone.roles.grant(os_role, user=user, project=project)
375
376
377 def get_domain_by_id(keystone, domain_id):
378     """
379     Returns the first OpenStack domain with the given name else None
380     :param keystone: the Keystone client
381     :param domain_id: the domain ID to retrieve
382     :return: the SNAPS-OO Domain domain object
383     """
384     domain = keystone.domains.get(domain_id)
385     if domain:
386         return Domain(name=domain.name, domain_id=domain.id)
387
388
389 def __get_os_domain_by_name(keystone, domain_name):
390     """
391     Returns the first OpenStack domain with the given name else None
392     :param keystone: the Keystone client
393     :param domain_name: the domain name to lookup
394     :return: the OpenStack domain object
395     """
396     domains = keystone.domains.list(name=domain_name)
397     for domain in domains:
398         if domain.name == domain_name:
399             return domain
400
401
402 class KeystoneException(Exception):
403     """
404     Exception when calls to the Keystone client cannot be served properly
405     """