e070a4954e4b04d26937846cf91c519c688e4df6
[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                            project_domain_id=os_creds.project_domain_id)
45     else:
46         auth = v2.Password(auth_url=os_creds.auth_url,
47                            username=os_creds.username,
48                            password=os_creds.password,
49                            tenant_name=os_creds.project_name)
50     return auth
51
52
53 def keystone_session(os_creds):
54     """
55     Creates a keystone session used for authenticating OpenStack clients
56     :param os_creds: The connection credentials to the OpenStack API
57     :return: the client object
58     """
59     logger.debug('Retrieving Keystone Session')
60
61     auth = get_session_auth(os_creds)
62
63     req_session = None
64     if os_creds.proxy_settings:
65         req_session = requests.Session()
66         req_session.proxies = {
67             'http':
68                 os_creds.proxy_settings.host + ':' +
69                 os_creds.proxy_settings.port,
70             'https':
71                 os_creds.proxy_settings.https_host + ':' +
72                 os_creds.proxy_settings.https_port
73         }
74     return session.Session(auth=auth, session=req_session,
75                            verify=os_creds.cacert)
76
77
78 def keystone_client(os_creds):
79     """
80     Returns the keystone client
81     :param os_creds: the OpenStack credentials (OSCreds) object
82     :return: the client
83     """
84     return Client(
85         version=os_creds.identity_api_version,
86         session=keystone_session(os_creds),
87         interface=os_creds.interface,
88         region_name=os_creds.region_name)
89
90
91 def get_endpoint(os_creds, service_type, interface='public'):
92     """
93     Returns the endpoint of specific service
94     :param os_creds: the OpenStack credentials (OSCreds) object
95     :param service_type: the type of specific service
96     :param interface: the type of interface
97     :return: the endpoint url
98     """
99     auth = get_session_auth(os_creds)
100     key_session = keystone_session(os_creds)
101     return key_session.get_endpoint(
102         auth=auth, service_type=service_type, interface=interface)
103
104
105 def get_project(keystone=None, os_creds=None, project_name=None):
106     """
107     Returns the first project object or None if not found
108     :param keystone: the Keystone client
109     :param os_creds: the OpenStack credentials used to obtain the Keystone
110                      client if the keystone parameter is None
111     :param project_name: the name to query
112     :return: the SNAPS-OO Project domain object or None
113     """
114     if not project_name:
115         return None
116
117     if not keystone:
118         if os_creds:
119             keystone = keystone_client(os_creds)
120         else:
121             raise KeystoneException(
122                 'Cannot lookup project without the proper credentials')
123
124     if keystone.version == V2_VERSION_STR:
125         projects = keystone.tenants.list()
126     else:
127         projects = keystone.projects.list(**{'name': project_name})
128
129     for project in projects:
130         domain_id = None
131         if keystone.version != V2_VERSION_STR:
132             domain_id = project.domain_id
133         if project.name == project_name:
134             return Project(name=project.name, project_id=project.id,
135                            domain_id=domain_id)
136
137     return None
138
139
140 def create_project(keystone, project_settings):
141     """
142     Creates a project
143     :param keystone: the Keystone client
144     :param project_settings: the project configuration
145     :return: SNAPS-OO Project domain object
146     """
147     domain_id = None
148
149     if keystone.version == V2_VERSION_STR:
150         os_project = keystone.tenants.create(
151             project_settings.name, project_settings.description,
152             project_settings.enabled)
153     else:
154         os_project = keystone.projects.create(
155             project_settings.name, project_settings.domain,
156             description=project_settings.description,
157             enabled=project_settings.enabled)
158         domain_id = os_project.domain_id
159
160     return Project(
161         name=os_project.name, project_id=os_project.id, domain_id=domain_id)
162
163
164 def delete_project(keystone, project):
165     """
166     Deletes a project
167     :param keystone: the Keystone clien
168     :param project: the SNAPS-OO Project domain object
169     """
170     if keystone.version == V2_VERSION_STR:
171         keystone.tenants.delete(project.id)
172     else:
173         keystone.projects.delete(project.id)
174
175
176 def __get_os_user(keystone, user):
177     """
178     Returns the OpenStack user object
179     :param keystone: the Keystone client object
180     :param user: the SNAPS-OO User domain object
181     :return:
182     """
183     return keystone.users.get(user.id)
184
185
186 def get_user(keystone, username, project_name=None):
187     """
188     Returns a user for a given name and optionally project
189     :param keystone: the keystone client
190     :param username: the username to lookup
191     :param project_name: the associated project (optional)
192     :return: a SNAPS-OO User domain object or None
193     """
194     project = get_project(keystone=keystone, project_name=project_name)
195
196     if project:
197         users = keystone.users.list(tenant_id=project.id)
198     else:
199         users = keystone.users.list()
200
201     for user in users:
202         if user.name == username:
203             return User(name=user.name, user_id=user.id)
204
205     return None
206
207
208 def create_user(keystone, user_settings):
209     """
210     Creates a user
211     :param keystone: the Keystone client
212     :param user_settings: the user configuration
213     :return: a SNAPS-OO User domain object
214     """
215     project = None
216     if user_settings.project_name:
217         project = get_project(keystone=keystone,
218                               project_name=user_settings.project_name)
219
220     if keystone.version == V2_VERSION_STR:
221         project_id = None
222         if project:
223             project_id = project.id
224         os_user = keystone.users.create(
225             name=user_settings.name, password=user_settings.password,
226             email=user_settings.email, tenant_id=project_id,
227             enabled=user_settings.enabled)
228     else:
229         os_user = keystone.users.create(
230             name=user_settings.name, password=user_settings.password,
231             email=user_settings.email, project=project,
232             domain=user_settings.domain_name, enabled=user_settings.enabled)
233
234     for role_name, role_project in user_settings.roles.items():
235         os_role = get_role_by_name(keystone, role_name)
236         os_project = get_project(keystone=keystone, project_name=role_project)
237
238         if os_role and os_project:
239             existing_roles = get_roles_by_user(keystone, os_user,
240                                                os_project)
241             found = False
242             for role in existing_roles:
243                 if role.id == os_role.id:
244                     found = True
245
246             if not found:
247                 grant_user_role_to_project(
248                     keystone=keystone, user=os_user, role=os_role,
249                     project=os_project)
250
251     if os_user:
252         return User(name=os_user.name, user_id=os_user.id)
253
254
255 def delete_user(keystone, user):
256     """
257     Deletes a user
258     :param keystone: the Keystone client
259     :param user: the SNAPS-OO User domain object
260     """
261     keystone.users.delete(user.id)
262
263
264 def get_role_by_name(keystone, name):
265     """
266     Returns an OpenStack role object of a given name or None if not exists
267     :param keystone: the keystone client
268     :param name: the role name
269     :return: the SNAPS-OO Role domain object
270     """
271     roles = keystone.roles.list()
272     for role in roles:
273         if role.name == name:
274             return Role(name=role.name, role_id=role.id)
275
276
277 def get_roles_by_user(keystone, user, project):
278     """
279     Returns a list of SNAPS-OO Role domain objects associated with a user
280     :param keystone: the keystone client
281     :param user: the OpenStack user object
282     :param project: the OpenStack project object (only required for v2)
283     :return: a list of SNAPS-OO Role domain objects
284     """
285     if keystone.version == V2_VERSION_STR:
286         os_user = __get_os_user(keystone, user)
287         roles = keystone.roles.roles_for_user(os_user, project)
288     else:
289         roles = keystone.roles.list(user=user, project=project)
290
291     out = list()
292     for role in roles:
293         out.append(Role(name=role.name, role_id=role.id))
294     return out
295
296
297 def get_role_by_id(keystone, role_id):
298     """
299     Returns an OpenStack role object of a given name or None if not exists
300     :param keystone: the keystone client
301     :param role_id: the role ID
302     :return: a SNAPS-OO Role domain object
303     """
304     role = keystone.roles.get(role_id)
305     return Role(name=role.name, role_id=role.id)
306
307
308 def create_role(keystone, name):
309     """
310     Creates an OpenStack role
311     :param keystone: the keystone client
312     :param name: the role name
313     :return: a SNAPS-OO Role domain object
314     """
315     role = keystone.roles.create(name)
316     return Role(name=role.name, role_id=role.id)
317
318
319 def delete_role(keystone, role):
320     """
321     Deletes an OpenStack role
322     :param keystone: the keystone client
323     :param role: the SNAPS-OO Role domain object to delete
324     :return:
325     """
326     keystone.roles.delete(role.id)
327
328
329 def grant_user_role_to_project(keystone, role, user, project):
330     """
331     Grants user and role to a project
332     :param keystone: the Keystone client
333     :param role: the SNAPS-OO Role domain object used to join a project/user
334     :param user: the user to add to the project (SNAPS-OO User Domain object
335     :param project: the project to which to add a user
336     :return:
337     """
338
339     os_role = get_role_by_id(keystone, role.id)
340     if keystone.version == V2_VERSION_STR:
341         keystone.roles.add_user_role(user, os_role, tenant=project)
342     else:
343         keystone.roles.grant(os_role, user=user, project=project)
344
345
346 class KeystoneException(Exception):
347     """
348     Exception when calls to the Keystone client cannot be served properly
349     """