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