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.
17 from keystoneclient.client import Client
18 from keystoneauth1.identity import v3, v2
19 from keystoneauth1 import session
22 from snaps.domain.project import Project, Domain
23 from snaps.domain.role import Role
24 from snaps.domain.user import User
26 logger = logging.getLogger('keystone_utils')
29 V2_VERSION_STR = 'v' + str(V2_VERSION_NUM)
32 def get_session_auth(os_creds):
34 Return the session auth for keystone session
35 :param os_creds: the OpenStack credentials (OSCreds) object
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)
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)
55 def keystone_session(os_creds):
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
61 logger.debug('Retrieving Keystone Session')
63 auth = get_session_auth(os_creds)
66 if os_creds.proxy_settings:
67 req_session = requests.Session()
68 req_session.proxies = {
70 os_creds.proxy_settings.host + ':' +
71 os_creds.proxy_settings.port,
73 os_creds.proxy_settings.https_host + ':' +
74 os_creds.proxy_settings.https_port
76 return session.Session(auth=auth, session=req_session,
77 verify=os_creds.cacert)
80 def keystone_client(os_creds):
82 Returns the keystone client
83 :param os_creds: the OpenStack credentials (OSCreds) object
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)
93 def get_endpoint(os_creds, service_type, interface='public'):
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
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, region_name=os_creds.region_name,
108 def get_project(keystone=None, os_creds=None, project_settings=None,
111 Returns the first project where the project_settings is used for the query
112 if not None, else the project_name parameter is used for the query. If both
113 parameters are None, None is returned
114 :param keystone: the Keystone client
115 :param os_creds: the OpenStack credentials used to obtain the Keystone
116 client if the keystone parameter is None
117 :param project_settings: a ProjectSettings object
118 :param project_name: the name to query
119 :return: the SNAPS-OO Project domain object or None
123 keystone = keystone_client(os_creds)
125 raise KeystoneException(
126 'Cannot lookup project without the proper credentials')
131 proj_filter['name'] = project_name
132 elif project_settings:
133 proj_filter['name'] = project_settings.name
134 proj_filter['description'] = project_settings.description
135 proj_filter['domain_name'] = project_settings.domain_name
136 proj_filter['enabled'] = project_settings.enabled
140 if keystone.version == V2_VERSION_STR:
141 projects = keystone.tenants.list()
143 projects = keystone.projects.list(**proj_filter)
145 for project in projects:
146 if project.name == proj_filter['name']:
148 if keystone.version != V2_VERSION_STR:
149 domain_id = project.domain_id
151 return Project(name=project.name, project_id=project.id,
155 def create_project(keystone, project_settings):
158 :param keystone: the Keystone client
159 :param project_settings: the project configuration
160 :return: SNAPS-OO Project domain object
164 if keystone.version == V2_VERSION_STR:
165 os_project = keystone.tenants.create(
166 project_settings.name, project_settings.description,
167 project_settings.enabled)
169 os_domain = __get_os_domain_by_name(
170 keystone, project_settings.domain_name)
172 os_domain = project_settings.domain_name
173 os_project = keystone.projects.create(
174 project_settings.name, os_domain,
175 description=project_settings.description,
176 enabled=project_settings.enabled)
177 domain_id = os_project.domain_id
179 logger.info('Created project with name - %s', project_settings.name)
181 name=os_project.name, project_id=os_project.id, domain_id=domain_id)
184 def delete_project(keystone, project):
187 :param keystone: the Keystone clien
188 :param project: the SNAPS-OO Project domain object
190 logger.info('Deleting project with name - %s', project.name)
191 if keystone.version == V2_VERSION_STR:
192 keystone.tenants.delete(project.id)
194 keystone.projects.delete(project.id)
197 def __get_os_user(keystone, user):
199 Returns the OpenStack user object
200 :param keystone: the Keystone client object
201 :param user: the SNAPS-OO User domain object
204 return keystone.users.get(user.id)
207 def get_user(keystone, username, project_name=None):
209 Returns a user for a given name and optionally project
210 :param keystone: the keystone client
211 :param username: the username to lookup
212 :param project_name: the associated project (optional)
213 :return: a SNAPS-OO User domain object or None
217 project = get_project(keystone=keystone, project_name=project_name)
220 users = keystone.users.list(tenant_id=project.id)
222 users = keystone.users.list()
225 if user.name == username:
226 return User(name=user.name, user_id=user.id)
231 def create_user(keystone, user_settings):
234 :param keystone: the Keystone client
235 :param user_settings: the user configuration
236 :return: a SNAPS-OO User domain object
239 if user_settings.project_name:
240 project = get_project(keystone=keystone,
241 project_name=user_settings.project_name)
243 if keystone.version == V2_VERSION_STR:
246 project_id = project.id
247 os_user = keystone.users.create(
248 name=user_settings.name, password=user_settings.password,
249 email=user_settings.email, tenant_id=project_id,
250 enabled=user_settings.enabled)
252 os_domain = __get_os_domain_by_name(
253 keystone, user_settings.domain_name)
255 os_domain = user_settings.domain_name
256 os_user = keystone.users.create(
257 name=user_settings.name, password=user_settings.password,
258 email=user_settings.email, project=project,
259 domain=os_domain, enabled=user_settings.enabled)
261 for role_name, role_project in user_settings.roles.items():
262 os_role = get_role_by_name(keystone, role_name)
263 os_project = get_project(keystone=keystone, project_name=role_project)
265 if os_role and os_project:
266 existing_roles = get_roles_by_user(keystone, os_user, os_project)
268 for role in existing_roles:
269 if role.id == os_role.id:
273 grant_user_role_to_project(
274 keystone=keystone, user=os_user, role=os_role,
278 logger.info('Created user with name - %s', os_user.name)
279 return User(name=os_user.name, user_id=os_user.id)
282 def delete_user(keystone, user):
285 :param keystone: the Keystone client
286 :param user: the SNAPS-OO User domain object
288 logger.info('Deleting user with name - %s', user.name)
289 keystone.users.delete(user.id)
292 def get_role_by_name(keystone, name):
294 Returns an OpenStack role object of a given name or None if not exists
295 :param keystone: the keystone client
296 :param name: the role name
297 :return: the SNAPS-OO Role domain object
299 roles = keystone.roles.list()
301 if role.name == name:
302 return Role(name=role.name, role_id=role.id)
305 def get_roles_by_user(keystone, user, project):
307 Returns a list of SNAPS-OO Role domain objects associated with a user
308 :param keystone: the keystone client
309 :param user: the OpenStack user object
310 :param project: the OpenStack project object (only required for v2)
311 :return: a list of SNAPS-OO Role domain objects
313 if keystone.version == V2_VERSION_STR:
314 os_user = __get_os_user(keystone, user)
315 roles = keystone.roles.roles_for_user(os_user, project)
317 roles = keystone.roles.list(user=user, project=project)
321 out.append(Role(name=role.name, role_id=role.id))
325 def get_role_by_id(keystone, role_id):
327 Returns an OpenStack role object of a given name or None if not exists
328 :param keystone: the keystone client
329 :param role_id: the role ID
330 :return: a SNAPS-OO Role domain object
332 role = keystone.roles.get(role_id)
333 return Role(name=role.name, role_id=role.id)
336 def create_role(keystone, name):
338 Creates an OpenStack role
339 :param keystone: the keystone client
340 :param name: the role name
341 :return: a SNAPS-OO Role domain object
343 role = keystone.roles.create(name)
344 logger.info('Created role with name - %s', role.name)
345 return Role(name=role.name, role_id=role.id)
348 def delete_role(keystone, role):
350 Deletes an OpenStack role
351 :param keystone: the keystone client
352 :param role: the SNAPS-OO Role domain object to delete
355 logger.info('Deleting role with name - %s', role.name)
356 keystone.roles.delete(role.id)
359 def grant_user_role_to_project(keystone, role, user, project):
361 Grants user and role to a project
362 :param keystone: the Keystone client
363 :param role: the SNAPS-OO Role domain object used to join a project/user
364 :param user: the user to add to the project (SNAPS-OO User Domain object
365 :param project: the project to which to add a user
369 os_role = get_role_by_id(keystone, role.id)
370 logger.info('Granting role %s to project %s', role.name, project)
371 if keystone.version == V2_VERSION_STR:
372 keystone.roles.add_user_role(user, os_role, tenant=project)
374 keystone.roles.grant(os_role, user=user, project=project)
377 def get_domain_by_id(keystone, domain_id):
379 Returns the first OpenStack domain with the given name else None
380 :param keystone: the Keystone client
381 :param domain_id: the domain ID to retrieve
382 :return: the SNAPS-OO Domain domain object
384 domain = keystone.domains.get(domain_id)
386 return Domain(name=domain.name, domain_id=domain.id)
389 def __get_os_domain_by_name(keystone, domain_name):
391 Returns the first OpenStack domain with the given name else None
392 :param keystone: the Keystone client
393 :param domain_name: the domain name to lookup
394 :return: the OpenStack domain object
396 domains = keystone.domains.list(name=domain_name)
397 for domain in domains:
398 if domain.name == domain_name:
402 class KeystoneException(Exception):
404 Exception when calls to the Keystone client cannot be served properly