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')
31 V2_VERSION_STR = 'v' + str(V2_VERSION_NUM)
34 def get_session_auth(os_creds):
36 Return the session auth for keystone session
37 :param os_creds: the OpenStack credentials (OSCreds) object
40 if os_creds.identity_api_version == 3:
41 auth = v3.Password(auth_url=os_creds.auth_url,
42 username=os_creds.username,
43 password=os_creds.password,
44 project_name=os_creds.project_name,
45 user_domain_id=os_creds.user_domain_id,
46 user_domain_name=os_creds.user_domain_name,
47 project_domain_id=os_creds.project_domain_id,
48 project_domain_name=os_creds.project_domain_name)
50 auth = v2.Password(auth_url=os_creds.auth_url,
51 username=os_creds.username,
52 password=os_creds.password,
53 tenant_name=os_creds.project_name)
57 def keystone_session(os_creds):
59 Creates a keystone session used for authenticating OpenStack clients
60 :param os_creds: The connection credentials to the OpenStack API
61 :return: the client object
63 logger.debug('Retrieving Keystone Session')
65 auth = get_session_auth(os_creds)
68 if os_creds.proxy_settings:
69 req_session = requests.Session()
70 req_session.proxies = {
72 os_creds.proxy_settings.host + ':' +
73 os_creds.proxy_settings.port,
75 os_creds.proxy_settings.https_host + ':' +
76 os_creds.proxy_settings.https_port
78 return session.Session(auth=auth, session=req_session,
79 verify=os_creds.cacert)
82 def close_session(session):
84 Closes a keystone session
85 :param session: a session.Session object
87 if isinstance(session, keystoneauth1.session.Session):
88 session.session.close()
91 def keystone_client(os_creds, session=None):
93 Returns the keystone client
94 :param os_creds: the OpenStack credentials (OSCreds) object
95 :param session: the keystone session object (optional)
100 session = keystone_session(os_creds)
103 version=os_creds.identity_api_version,
105 interface=os_creds.interface,
106 region_name=os_creds.region_name)
109 def get_endpoint(os_creds, service_type, interface='public'):
111 Returns the endpoint of specific service
112 :param os_creds: the OpenStack credentials (OSCreds) object
113 :param service_type: the type of specific service
114 :param interface: the type of interface
115 :return: the endpoint url
117 auth = get_session_auth(os_creds)
118 key_session = keystone_session(os_creds)
119 return key_session.get_endpoint(
120 auth=auth, service_type=service_type, region_name=os_creds.region_name,
124 def get_project(keystone=None, project_settings=None, project_name=None):
126 Returns the first project where the project_settings is used for the query
127 if not None, else the project_name parameter is used for the query. If both
128 parameters are None, None is returned
129 :param keystone: the Keystone client
130 :param project_settings: a ProjectConfig object
131 :param project_name: the name to query
132 :return: the SNAPS-OO Project domain object or None
137 proj_filter['name'] = project_name
138 elif project_settings:
139 proj_filter['name'] = project_settings.name
140 proj_filter['description'] = project_settings.description
141 proj_filter['domain_name'] = project_settings.domain_name
142 proj_filter['enabled'] = project_settings.enabled
146 if keystone.version == V2_VERSION_STR:
147 projects = keystone.tenants.list()
149 projects = keystone.projects.list(**proj_filter)
151 for project in projects:
152 if project.name == proj_filter['name']:
154 if keystone.version != V2_VERSION_STR:
155 domain_id = project.domain_id
157 return Project(name=project.name, project_id=project.id,
161 def get_project_by_id(keystone, proj_id):
163 Returns the first project where the project_settings is used for the query
164 if not None, else the project_name parameter is used for the query. If both
165 parameters are None, None is returned
166 :param keystone: the Keystone client
167 :param proj_id: the project ID
169 if proj_id and len(proj_id) > 0:
171 os_proj = keystone.projects.get(proj_id)
173 return Project(name=os_proj.name, project_id=os_proj.id,
181 def create_project(keystone, project_settings):
184 :param keystone: the Keystone client
185 :param project_settings: the project configuration
186 :return: SNAPS-OO Project domain object
190 if keystone.version == V2_VERSION_STR:
191 os_project = keystone.tenants.create(
192 project_settings.name, project_settings.description,
193 project_settings.enabled)
195 os_domain = __get_os_domain_by_name(
196 keystone, project_settings.domain_name)
198 os_domain = project_settings.domain_name
199 os_project = keystone.projects.create(
200 project_settings.name, os_domain,
201 description=project_settings.description,
202 enabled=project_settings.enabled)
203 domain_id = os_project.domain_id
205 logger.info('Created project with name - %s', project_settings.name)
207 name=os_project.name, project_id=os_project.id, domain_id=domain_id)
210 def delete_project(keystone, project):
213 :param keystone: the Keystone clien
214 :param project: the SNAPS-OO Project domain object
216 logger.info('Deleting project with name - %s', project.name)
217 if keystone.version == V2_VERSION_STR:
218 keystone.tenants.delete(project.id)
220 keystone.projects.delete(project.id)
223 def __get_os_user(keystone, user):
225 Returns the OpenStack user object
226 :param keystone: the Keystone client object
227 :param user: the SNAPS-OO User domain object
230 return keystone.users.get(user.id)
233 def get_user(keystone, username, project_name=None):
235 Returns a user for a given name and optionally project
236 :param keystone: the keystone client
237 :param username: the username to lookup
238 :param project_name: the associated project (optional)
239 :return: a SNAPS-OO User domain object or None
243 project = get_project(keystone=keystone, project_name=project_name)
246 users = keystone.users.list(tenant_id=project.id)
248 users = keystone.users.list()
251 if user.name == username:
252 return User(name=user.name, user_id=user.id)
257 def create_user(keystone, user_settings):
260 :param keystone: the Keystone client
261 :param user_settings: the user configuration
262 :return: a SNAPS-OO User domain object
265 if user_settings.project_name:
266 project = get_project(
267 keystone=keystone, project_name=user_settings.project_name)
269 if keystone.version == V2_VERSION_STR:
272 project_id = project.id
273 os_user = keystone.users.create(
274 name=user_settings.name, password=user_settings.password,
275 email=user_settings.email, tenant_id=project_id,
276 enabled=user_settings.enabled)
278 os_domain = __get_os_domain_by_name(
279 keystone, user_settings.domain_name)
281 os_domain = user_settings.domain_name
282 os_user = keystone.users.create(
283 name=user_settings.name, password=user_settings.password,
284 email=user_settings.email, project=project,
285 domain=os_domain, enabled=user_settings.enabled)
287 for role_name, role_project in user_settings.roles.items():
288 os_role = get_role_by_name(keystone, role_name)
289 os_project = get_project(keystone=keystone, project_name=role_project)
291 if os_role and os_project:
292 existing_roles = get_roles_by_user(keystone, os_user, os_project)
294 for role in existing_roles:
295 if role.id == os_role.id:
299 grant_user_role_to_project(
300 keystone=keystone, user=os_user, role=os_role,
304 logger.info('Created user with name - %s', os_user.name)
305 return User(name=os_user.name, user_id=os_user.id)
308 def delete_user(keystone, user):
311 :param keystone: the Keystone client
312 :param user: the SNAPS-OO User domain object
314 logger.info('Deleting user with name - %s', user.name)
315 keystone.users.delete(user.id)
318 def get_role_by_name(keystone, name):
320 Returns an OpenStack role object of a given name or None if not exists
321 :param keystone: the keystone client
322 :param name: the role name
323 :return: the SNAPS-OO Role domain object
325 roles = keystone.roles.list()
327 if role.name == name:
328 return Role(name=role.name, role_id=role.id)
331 def get_roles_by_user(keystone, user, project):
333 Returns a list of SNAPS-OO Role domain objects associated with a user
334 :param keystone: the keystone client
335 :param user: the OpenStack user object
336 :param project: the OpenStack project object (only required for v2)
337 :return: a list of SNAPS-OO Role domain objects
339 if keystone.version == V2_VERSION_STR:
340 os_user = __get_os_user(keystone, user)
341 roles = keystone.roles.roles_for_user(os_user, project)
343 roles = keystone.roles.list(user=user, project=project)
347 out.append(Role(name=role.name, role_id=role.id))
351 def get_role_by_id(keystone, role_id):
353 Returns an OpenStack role object of a given name or None if not exists
354 :param keystone: the keystone client
355 :param role_id: the role ID
356 :return: a SNAPS-OO Role domain object
358 role = keystone.roles.get(role_id)
359 return Role(name=role.name, role_id=role.id)
362 def create_role(keystone, name):
364 Creates an OpenStack role
365 :param keystone: the keystone client
366 :param name: the role name
367 :return: a SNAPS-OO Role domain object
369 role = keystone.roles.create(name)
370 logger.info('Created role with name - %s', role.name)
371 return Role(name=role.name, role_id=role.id)
374 def delete_role(keystone, role):
376 Deletes an OpenStack role
377 :param keystone: the keystone client
378 :param role: the SNAPS-OO Role domain object to delete
381 logger.info('Deleting role with name - %s', role.name)
382 keystone.roles.delete(role.id)
385 def grant_user_role_to_project(keystone, role, user, project):
387 Grants user and role to a project
388 :param keystone: the Keystone client
389 :param role: the SNAPS-OO Role domain object used to join a project/user
390 :param user: the user to add to the project (SNAPS-OO User Domain object
391 :param project: the project to which to add a user
395 os_role = get_role_by_id(keystone, role.id)
396 logger.info('Granting role %s to project %s', role.name, project.name)
397 if keystone.version == V2_VERSION_STR:
398 keystone.roles.add_user_role(user, os_role, tenant=project)
400 keystone.roles.grant(os_role, user=user, project=project)
403 def get_domain_by_id(keystone, domain_id):
405 Returns the first OpenStack domain with the given name else None
406 :param keystone: the Keystone client
407 :param domain_id: the domain ID to retrieve
408 :return: the SNAPS-OO Domain domain object
410 if keystone.version != V2_VERSION_STR:
411 domain = keystone.domains.get(domain_id)
413 return Domain(name=domain.name, domain_id=domain.id)
416 def __get_os_domain_by_name(keystone, domain_name):
418 Returns the first OpenStack domain with the given name else None
419 :param keystone: the Keystone client
420 :param domain_name: the domain name to lookup
421 :return: the OpenStack domain object
423 domains = keystone.domains.list(name=domain_name)
424 for domain in domains:
425 if domain.name == domain_name:
429 class KeystoneException(Exception):
431 Exception when calls to the Keystone client cannot be served properly