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
21 from keystoneclient.exceptions import NotFound
23 from snaps.domain.project import Project, Domain
24 from snaps.domain.role import Role
25 from snaps.domain.user import User
27 logger = logging.getLogger('keystone_utils')
30 V2_VERSION_STR = 'v' + str(V2_VERSION_NUM)
33 def get_session_auth(os_creds):
35 Return the session auth for keystone session
36 :param os_creds: the OpenStack credentials (OSCreds) object
39 if os_creds.identity_api_version == 3:
40 auth = v3.Password(auth_url=os_creds.auth_url,
41 username=os_creds.username,
42 password=os_creds.password,
43 project_name=os_creds.project_name,
44 user_domain_id=os_creds.user_domain_id,
45 user_domain_name=os_creds.user_domain_name,
46 project_domain_id=os_creds.project_domain_id,
47 project_domain_name=os_creds.project_domain_name)
49 auth = v2.Password(auth_url=os_creds.auth_url,
50 username=os_creds.username,
51 password=os_creds.password,
52 tenant_name=os_creds.project_name)
56 def keystone_session(os_creds):
58 Creates a keystone session used for authenticating OpenStack clients
59 :param os_creds: The connection credentials to the OpenStack API
60 :return: the client object
62 logger.debug('Retrieving Keystone Session')
64 auth = get_session_auth(os_creds)
67 if os_creds.proxy_settings:
68 req_session = requests.Session()
69 req_session.proxies = {
71 os_creds.proxy_settings.host + ':' +
72 os_creds.proxy_settings.port,
74 os_creds.proxy_settings.https_host + ':' +
75 os_creds.proxy_settings.https_port
77 return session.Session(auth=auth, session=req_session,
78 verify=os_creds.cacert)
81 def keystone_client(os_creds):
83 Returns the keystone client
84 :param os_creds: the OpenStack credentials (OSCreds) object
88 version=os_creds.identity_api_version,
89 session=keystone_session(os_creds),
90 interface=os_creds.interface,
91 region_name=os_creds.region_name)
94 def get_endpoint(os_creds, service_type, interface='public'):
96 Returns the endpoint of specific service
97 :param os_creds: the OpenStack credentials (OSCreds) object
98 :param service_type: the type of specific service
99 :param interface: the type of interface
100 :return: the endpoint url
102 auth = get_session_auth(os_creds)
103 key_session = keystone_session(os_creds)
104 return key_session.get_endpoint(
105 auth=auth, service_type=service_type, region_name=os_creds.region_name,
109 def get_project(keystone=None, project_settings=None, project_name=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 project_settings: a ProjectConfig object
116 :param project_name: the name to query
117 :return: the SNAPS-OO Project domain object or None
122 proj_filter['name'] = project_name
123 elif project_settings:
124 proj_filter['name'] = project_settings.name
125 proj_filter['description'] = project_settings.description
126 proj_filter['domain_name'] = project_settings.domain_name
127 proj_filter['enabled'] = project_settings.enabled
131 if keystone.version == V2_VERSION_STR:
132 projects = keystone.tenants.list()
134 projects = keystone.projects.list(**proj_filter)
136 for project in projects:
137 if project.name == proj_filter['name']:
139 if keystone.version != V2_VERSION_STR:
140 domain_id = project.domain_id
142 return Project(name=project.name, project_id=project.id,
146 def get_project_by_id(keystone, proj_id):
148 Returns the first project where the project_settings is used for the query
149 if not None, else the project_name parameter is used for the query. If both
150 parameters are None, None is returned
151 :param keystone: the Keystone client
152 :param proj_id: the project ID
154 if proj_id and len(proj_id) > 0:
156 os_proj = keystone.projects.get(proj_id)
158 return Project(name=os_proj.name, project_id=os_proj.id,
166 def create_project(keystone, project_settings):
169 :param keystone: the Keystone client
170 :param project_settings: the project configuration
171 :return: SNAPS-OO Project domain object
175 if keystone.version == V2_VERSION_STR:
176 os_project = keystone.tenants.create(
177 project_settings.name, project_settings.description,
178 project_settings.enabled)
180 os_domain = __get_os_domain_by_name(
181 keystone, project_settings.domain_name)
183 os_domain = project_settings.domain_name
184 os_project = keystone.projects.create(
185 project_settings.name, os_domain,
186 description=project_settings.description,
187 enabled=project_settings.enabled)
188 domain_id = os_project.domain_id
190 logger.info('Created project with name - %s', project_settings.name)
192 name=os_project.name, project_id=os_project.id, domain_id=domain_id)
195 def delete_project(keystone, project):
198 :param keystone: the Keystone clien
199 :param project: the SNAPS-OO Project domain object
201 logger.info('Deleting project with name - %s', project.name)
202 if keystone.version == V2_VERSION_STR:
203 keystone.tenants.delete(project.id)
205 keystone.projects.delete(project.id)
208 def __get_os_user(keystone, user):
210 Returns the OpenStack user object
211 :param keystone: the Keystone client object
212 :param user: the SNAPS-OO User domain object
215 return keystone.users.get(user.id)
218 def get_user(keystone, username, project_name=None):
220 Returns a user for a given name and optionally project
221 :param keystone: the keystone client
222 :param username: the username to lookup
223 :param project_name: the associated project (optional)
224 :return: a SNAPS-OO User domain object or None
228 project = get_project(keystone=keystone, project_name=project_name)
231 users = keystone.users.list(tenant_id=project.id)
233 users = keystone.users.list()
236 if user.name == username:
237 return User(name=user.name, user_id=user.id)
242 def create_user(keystone, user_settings):
245 :param keystone: the Keystone client
246 :param user_settings: the user configuration
247 :return: a SNAPS-OO User domain object
250 if user_settings.project_name:
251 project = get_project(
252 keystone=keystone, project_name=user_settings.project_name)
254 if keystone.version == V2_VERSION_STR:
257 project_id = project.id
258 os_user = keystone.users.create(
259 name=user_settings.name, password=user_settings.password,
260 email=user_settings.email, tenant_id=project_id,
261 enabled=user_settings.enabled)
263 os_domain = __get_os_domain_by_name(
264 keystone, user_settings.domain_name)
266 os_domain = user_settings.domain_name
267 os_user = keystone.users.create(
268 name=user_settings.name, password=user_settings.password,
269 email=user_settings.email, project=project,
270 domain=os_domain, enabled=user_settings.enabled)
272 for role_name, role_project in user_settings.roles.items():
273 os_role = get_role_by_name(keystone, role_name)
274 os_project = get_project(keystone=keystone, project_name=role_project)
276 if os_role and os_project:
277 existing_roles = get_roles_by_user(keystone, os_user, os_project)
279 for role in existing_roles:
280 if role.id == os_role.id:
284 grant_user_role_to_project(
285 keystone=keystone, user=os_user, role=os_role,
289 logger.info('Created user with name - %s', os_user.name)
290 return User(name=os_user.name, user_id=os_user.id)
293 def delete_user(keystone, user):
296 :param keystone: the Keystone client
297 :param user: the SNAPS-OO User domain object
299 logger.info('Deleting user with name - %s', user.name)
300 keystone.users.delete(user.id)
303 def get_role_by_name(keystone, name):
305 Returns an OpenStack role object of a given name or None if not exists
306 :param keystone: the keystone client
307 :param name: the role name
308 :return: the SNAPS-OO Role domain object
310 roles = keystone.roles.list()
312 if role.name == name:
313 return Role(name=role.name, role_id=role.id)
316 def get_roles_by_user(keystone, user, project):
318 Returns a list of SNAPS-OO Role domain objects associated with a user
319 :param keystone: the keystone client
320 :param user: the OpenStack user object
321 :param project: the OpenStack project object (only required for v2)
322 :return: a list of SNAPS-OO Role domain objects
324 if keystone.version == V2_VERSION_STR:
325 os_user = __get_os_user(keystone, user)
326 roles = keystone.roles.roles_for_user(os_user, project)
328 roles = keystone.roles.list(user=user, project=project)
332 out.append(Role(name=role.name, role_id=role.id))
336 def get_role_by_id(keystone, role_id):
338 Returns an OpenStack role object of a given name or None if not exists
339 :param keystone: the keystone client
340 :param role_id: the role ID
341 :return: a SNAPS-OO Role domain object
343 role = keystone.roles.get(role_id)
344 return Role(name=role.name, role_id=role.id)
347 def create_role(keystone, name):
349 Creates an OpenStack role
350 :param keystone: the keystone client
351 :param name: the role name
352 :return: a SNAPS-OO Role domain object
354 role = keystone.roles.create(name)
355 logger.info('Created role with name - %s', role.name)
356 return Role(name=role.name, role_id=role.id)
359 def delete_role(keystone, role):
361 Deletes an OpenStack role
362 :param keystone: the keystone client
363 :param role: the SNAPS-OO Role domain object to delete
366 logger.info('Deleting role with name - %s', role.name)
367 keystone.roles.delete(role.id)
370 def grant_user_role_to_project(keystone, role, user, project):
372 Grants user and role to a project
373 :param keystone: the Keystone client
374 :param role: the SNAPS-OO Role domain object used to join a project/user
375 :param user: the user to add to the project (SNAPS-OO User Domain object
376 :param project: the project to which to add a user
380 os_role = get_role_by_id(keystone, role.id)
381 logger.info('Granting role %s to project %s', role.name, project.name)
382 if keystone.version == V2_VERSION_STR:
383 keystone.roles.add_user_role(user, os_role, tenant=project)
385 keystone.roles.grant(os_role, user=user, project=project)
388 def get_domain_by_id(keystone, domain_id):
390 Returns the first OpenStack domain with the given name else None
391 :param keystone: the Keystone client
392 :param domain_id: the domain ID to retrieve
393 :return: the SNAPS-OO Domain domain object
395 if keystone.version != V2_VERSION_STR:
396 domain = keystone.domains.get(domain_id)
398 return Domain(name=domain.name, domain_id=domain.id)
401 def __get_os_domain_by_name(keystone, domain_name):
403 Returns the first OpenStack domain with the given name else None
404 :param keystone: the Keystone client
405 :param domain_name: the domain name to lookup
406 :return: the OpenStack domain object
408 domains = keystone.domains.list(name=domain_name)
409 for domain in domains:
410 if domain.name == domain_name:
414 class KeystoneException(Exception):
416 Exception when calls to the Keystone client cannot be served properly