3fff4695e7a2b3be268f4724440861dc01c4fdcd
[snaps.git] / snaps / openstack / utils / keystone_utils.py
1 # Copyright (c) 2016 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
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, interface=interface)
105
106
107 def get_project(keystone=None, os_creds=None, project_name=None):
108     """
109     Returns the first project object or None if not found
110     :param keystone: the Keystone client
111     :param os_creds: the OpenStack credentials used to obtain the Keystone
112                      client if the keystone parameter is None
113     :param project_name: the name to query
114     :return: the SNAPS-OO Project domain object or None
115     """
116     if not project_name:
117         return None
118
119     if not keystone:
120         if os_creds:
121             keystone = keystone_client(os_creds)
122         else:
123             raise KeystoneException(
124                 'Cannot lookup project without the proper credentials')
125
126     if keystone.version == V2_VERSION_STR:
127         projects = keystone.tenants.list()
128     else:
129         projects = keystone.projects.list(**{'name': project_name})
130
131     for project in projects:
132         domain_id = None
133         if keystone.version != V2_VERSION_STR:
134             domain_id = project.domain_id
135         if project.name == project_name:
136             return Project(name=project.name, project_id=project.id,
137                            domain_id=domain_id)
138
139     return None
140
141
142 def create_project(keystone, project_settings):
143     """
144     Creates a project
145     :param keystone: the Keystone client
146     :param project_settings: the project configuration
147     :return: SNAPS-OO Project domain object
148     """
149     domain_id = None
150
151     if keystone.version == V2_VERSION_STR:
152         os_project = keystone.tenants.create(
153             project_settings.name, project_settings.description,
154             project_settings.enabled)
155     else:
156         os_project = keystone.projects.create(
157             project_settings.name, project_settings.domain,
158             description=project_settings.description,
159             enabled=project_settings.enabled)
160         domain_id = os_project.domain_id
161
162     return Project(
163         name=os_project.name, project_id=os_project.id, domain_id=domain_id)
164
165
166 def delete_project(keystone, project):
167     """
168     Deletes a project
169     :param keystone: the Keystone clien
170     :param project: the SNAPS-OO Project domain object
171     """
172     if keystone.version == V2_VERSION_STR:
173         keystone.tenants.delete(project.id)
174     else:
175         keystone.projects.delete(project.id)
176
177
178 def __get_os_user(keystone, user):
179     """
180     Returns the OpenStack user object
181     :param keystone: the Keystone client object
182     :param user: the SNAPS-OO User domain object
183     :return:
184     """
185     return keystone.users.get(user.id)
186
187
188 def get_user(keystone, username, project_name=None):
189     """
190     Returns a user for a given name and optionally project
191     :param keystone: the keystone client
192     :param username: the username to lookup
193     :param project_name: the associated project (optional)
194     :return: a SNAPS-OO User domain object or None
195     """
196     project = get_project(keystone=keystone, project_name=project_name)
197
198     if project:
199         users = keystone.users.list(tenant_id=project.id)
200     else:
201         users = keystone.users.list()
202
203     for user in users:
204         if user.name == username:
205             return User(name=user.name, user_id=user.id)
206
207     return None
208
209
210 def create_user(keystone, user_settings):
211     """
212     Creates a user
213     :param keystone: the Keystone client
214     :param user_settings: the user configuration
215     :return: a SNAPS-OO User domain object
216     """
217     project = None
218     if user_settings.project_name:
219         project = get_project(keystone=keystone,
220                               project_name=user_settings.project_name)
221
222     if keystone.version == V2_VERSION_STR:
223         project_id = None
224         if project:
225             project_id = project.id
226         os_user = keystone.users.create(
227             name=user_settings.name, password=user_settings.password,
228             email=user_settings.email, tenant_id=project_id,
229             enabled=user_settings.enabled)
230     else:
231         os_user = keystone.users.create(
232             name=user_settings.name, password=user_settings.password,
233             email=user_settings.email, project=project,
234             domain=user_settings.domain_name, enabled=user_settings.enabled)
235
236     for role_name, role_project in user_settings.roles.items():
237         os_role = get_role_by_name(keystone, role_name)
238         os_project = get_project(keystone=keystone, project_name=role_project)
239
240         if os_role and os_project:
241             existing_roles = get_roles_by_user(keystone, os_user,
242                                                os_project)
243             found = False
244             for role in existing_roles:
245                 if role.id == os_role.id:
246                     found = True
247
248             if not found:
249                 grant_user_role_to_project(
250                     keystone=keystone, user=os_user, role=os_role,
251                     project=os_project)
252
253     if os_user:
254         return User(name=os_user.name, user_id=os_user.id)
255
256
257 def delete_user(keystone, user):
258     """
259     Deletes a user
260     :param keystone: the Keystone client
261     :param user: the SNAPS-OO User domain object
262     """
263     keystone.users.delete(user.id)
264
265
266 def get_role_by_name(keystone, name):
267     """
268     Returns an OpenStack role object of a given name or None if not exists
269     :param keystone: the keystone client
270     :param name: the role name
271     :return: the SNAPS-OO Role domain object
272     """
273     roles = keystone.roles.list()
274     for role in roles:
275         if role.name == name:
276             return Role(name=role.name, role_id=role.id)
277
278
279 def get_roles_by_user(keystone, user, project):
280     """
281     Returns a list of SNAPS-OO Role domain objects associated with a user
282     :param keystone: the keystone client
283     :param user: the OpenStack user object
284     :param project: the OpenStack project object (only required for v2)
285     :return: a list of SNAPS-OO Role domain objects
286     """
287     if keystone.version == V2_VERSION_STR:
288         os_user = __get_os_user(keystone, user)
289         roles = keystone.roles.roles_for_user(os_user, project)
290     else:
291         roles = keystone.roles.list(user=user, project=project)
292
293     out = list()
294     for role in roles:
295         out.append(Role(name=role.name, role_id=role.id))
296     return out
297
298
299 def get_role_by_id(keystone, role_id):
300     """
301     Returns an OpenStack role object of a given name or None if not exists
302     :param keystone: the keystone client
303     :param role_id: the role ID
304     :return: a SNAPS-OO Role domain object
305     """
306     role = keystone.roles.get(role_id)
307     return Role(name=role.name, role_id=role.id)
308
309
310 def create_role(keystone, name):
311     """
312     Creates an OpenStack role
313     :param keystone: the keystone client
314     :param name: the role name
315     :return: a SNAPS-OO Role domain object
316     """
317     role = keystone.roles.create(name)
318     return Role(name=role.name, role_id=role.id)
319
320
321 def delete_role(keystone, role):
322     """
323     Deletes an OpenStack role
324     :param keystone: the keystone client
325     :param role: the SNAPS-OO Role domain object to delete
326     :return:
327     """
328     keystone.roles.delete(role.id)
329
330
331 def grant_user_role_to_project(keystone, role, user, project):
332     """
333     Grants user and role to a project
334     :param keystone: the Keystone client
335     :param role: the SNAPS-OO Role domain object used to join a project/user
336     :param user: the user to add to the project (SNAPS-OO User Domain object
337     :param project: the project to which to add a user
338     :return:
339     """
340
341     os_role = get_role_by_id(keystone, role.id)
342     if keystone.version == V2_VERSION_STR:
343         keystone.roles.add_user_role(user, os_role, tenant=project)
344     else:
345         keystone.roles.grant(os_role, user=user, project=project)
346
347
348 class KeystoneException(Exception):
349     """
350     Exception when calls to the Keystone client cannot be served properly
351     """