1 # Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
2 # and others. All rights reserved.
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:
8 # http://www.apache.org/licenses/LICENSE-2.0
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.
18 from keystoneclient.client import Client
19 from keystoneauth1.identity import v3, v2
20 from keystoneauth1 import session
22 from keystoneclient.exceptions import NotFound
24 from snaps.domain.project import Project, Domain
25 from snaps.domain.role import Role
26 from snaps.domain.user import User
28 logger = logging.getLogger('keystone_utils')
32 V2_VERSION_STR = 'v' + str(V2_VERSION_NUM)
35 def get_session_auth(os_creds):
37 Return the session auth for keystone session
38 :param os_creds: the OpenStack credentials (OSCreds) object
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)
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)
58 def keystone_session(os_creds):
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
64 logger.debug('Retrieving Keystone Session')
66 auth = get_session_auth(os_creds)
69 if os_creds.proxy_settings:
70 req_session = requests.Session()
71 req_session.proxies = {
73 os_creds.proxy_settings.host + ':' +
74 os_creds.proxy_settings.port,
76 os_creds.proxy_settings.https_host + ':' +
77 os_creds.proxy_settings.https_port
79 return session.Session(auth=auth, session=req_session,
80 verify=os_creds.cacert)
83 def close_session(session):
85 Closes a keystone session
86 :param session: a session.Session object
88 if isinstance(session, keystoneauth1.session.Session):
89 session.session.close()
92 def keystone_client(os_creds, session=None):
94 Returns the keystone client
95 :param os_creds: the OpenStack credentials (OSCreds) object
96 :param session: the keystone session object (optional)
101 session = keystone_session(os_creds)
104 version=os_creds.identity_api_version,
106 interface=os_creds.interface,
107 region_name=os_creds.region_name)
110 def get_endpoint(os_creds, service_type, interface='public'):
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
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,
125 def get_project(keystone=None, project_settings=None, project_name=None):
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
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
147 if keystone.version == V2_VERSION_STR:
148 projects = keystone.tenants.list()
150 projects = keystone.projects.list(**proj_filter)
152 for project in projects:
153 if project.name == proj_filter['name']:
155 if keystone.version != V2_VERSION_STR:
156 domain_id = project.domain_id
158 return Project(name=project.name, project_id=project.id,
162 def get_project_by_id(keystone, proj_id):
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
170 if proj_id and len(proj_id) > 0:
172 os_proj = keystone.projects.get(proj_id)
174 return Project(name=os_proj.name, project_id=os_proj.id,
182 def create_project(keystone, project_settings):
185 :param keystone: the Keystone client
186 :param project_settings: the project configuration
187 :return: SNAPS-OO Project domain object
191 if keystone.version == V2_VERSION_STR:
192 os_project = keystone.tenants.create(
193 project_settings.name, project_settings.description,
194 project_settings.enabled)
196 os_domain = __get_os_domain_by_name(
197 keystone, project_settings.domain_name)
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
206 logger.info('Created project with name - %s', project_settings.name)
208 name=os_project.name, project_id=os_project.id, domain_id=domain_id)
211 def delete_project(keystone, project):
214 :param keystone: the Keystone client
215 :param project: the SNAPS-OO Project domain object
217 logger.info('Deleting project with name - %s', project.name)
218 if keystone.version == V2_VERSION_STR:
219 keystone.tenants.delete(project.id)
221 keystone.projects.delete(project.id)
224 def __get_os_user(keystone, user):
226 Returns the OpenStack user object
227 :param keystone: the Keystone client object
228 :param user: the SNAPS-OO User domain object
231 return keystone.users.get(user.id)
234 def get_user(keystone, username, project_name=None):
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
244 project = get_project(keystone=keystone, project_name=project_name)
247 users = keystone.users.list(tenant_id=project.id)
249 users = keystone.users.list()
252 if user.name == username:
253 return User(name=user.name, user_id=user.id)
258 def create_user(keystone, user_settings):
261 :param keystone: the Keystone client
262 :param user_settings: the user configuration
263 :return: a SNAPS-OO User domain object
266 if user_settings.project_name:
267 project = get_project(
268 keystone=keystone, project_name=user_settings.project_name)
270 if keystone.version == V2_VERSION_STR:
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)
279 os_domain = __get_os_domain_by_name(
280 keystone, user_settings.domain_name)
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)
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)
292 if os_role and os_project:
293 existing_roles = get_roles_by_user(keystone, os_user, os_project)
295 for role in existing_roles:
296 if role.id == os_role.id:
300 grant_user_role_to_project(
301 keystone=keystone, user=os_user, role=os_role,
305 logger.info('Created user with name - %s', os_user.name)
306 return User(name=os_user.name, user_id=os_user.id)
309 def delete_user(keystone, user):
312 :param keystone: the Keystone client
313 :param user: the SNAPS-OO User domain object
315 logger.info('Deleting user with name - %s', user.name)
316 keystone.users.delete(user.id)
319 def get_role_by_name(keystone, name):
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
326 roles = keystone.roles.list()
328 if role.name == name:
329 return Role(name=role.name, role_id=role.id)
332 def get_roles_by_user(keystone, user, project):
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
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)
344 roles = keystone.roles.list(user=user, project=project)
348 out.append(Role(name=role.name, role_id=role.id))
352 def get_role_by_id(keystone, role_id):
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
359 role = keystone.roles.get(role_id)
360 return Role(name=role.name, role_id=role.id)
363 def create_role(keystone, name):
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
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)
375 def delete_role(keystone, role):
377 Deletes an OpenStack role
378 :param keystone: the keystone client
379 :param role: the SNAPS-OO Role domain object to delete
382 logger.info('Deleting role with name - %s', role.name)
383 keystone.roles.delete(role.id)
386 def grant_user_role_to_project(keystone, role, user, project):
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
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)
401 keystone.roles.grant(os_role, user=user, project=project)
404 def get_domain_by_id(keystone, domain_id):
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
411 if keystone.version != V2_VERSION_STR:
412 domain = keystone.domains.get(domain_id)
414 return Domain(name=domain.name, domain_id=domain.id)
417 def __get_os_domain_by_name(keystone, domain_name):
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
424 domains = keystone.domains.list(name=domain_name)
425 for domain in domains:
426 if domain.name == domain_name:
430 class KeystoneException(Exception):
432 Exception when calls to the Keystone client cannot be served properly