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, interface=interface)
107 def get_project(keystone=None, os_creds=None, project_settings=None,
110 Returns the first project object or None if not found
111 :param keystone: the Keystone client
112 :param os_creds: the OpenStack credentials used to obtain the Keystone
113 client if the keystone parameter is None
114 :param project_settings: a ProjectSettings object
115 :param project_name: the name to query
116 :return: the SNAPS-OO Project domain object or None
120 keystone = keystone_client(os_creds)
122 raise KeystoneException(
123 'Cannot lookup project without the proper credentials')
128 proj_filter['name'] = project_name
129 elif project_settings:
130 proj_filter['name'] = project_settings.name
131 proj_filter['description'] = project_settings.description
132 proj_filter['domain_name'] = project_settings.domain_name
133 proj_filter['enabled'] = project_settings.enabled
135 if keystone.version == V2_VERSION_STR:
136 projects = keystone.tenants.list()
138 projects = keystone.projects.list(**proj_filter)
140 for project in projects:
141 if project.name == proj_filter['name']:
143 if keystone.version != V2_VERSION_STR:
144 domain_id = project.domain_id
146 return Project(name=project.name, project_id=project.id,
150 def create_project(keystone, project_settings):
153 :param keystone: the Keystone client
154 :param project_settings: the project configuration
155 :return: SNAPS-OO Project domain object
159 if keystone.version == V2_VERSION_STR:
160 os_project = keystone.tenants.create(
161 project_settings.name, project_settings.description,
162 project_settings.enabled)
164 os_domain = __get_os_domain_by_name(
165 keystone, project_settings.domain_name)
167 os_domain = project_settings.domain_name
168 os_project = keystone.projects.create(
169 project_settings.name, os_domain,
170 description=project_settings.description,
171 enabled=project_settings.enabled)
172 domain_id = os_project.domain_id
175 name=os_project.name, project_id=os_project.id, domain_id=domain_id)
178 def delete_project(keystone, project):
181 :param keystone: the Keystone clien
182 :param project: the SNAPS-OO Project domain object
184 if keystone.version == V2_VERSION_STR:
185 keystone.tenants.delete(project.id)
187 keystone.projects.delete(project.id)
190 def __get_os_user(keystone, user):
192 Returns the OpenStack user object
193 :param keystone: the Keystone client object
194 :param user: the SNAPS-OO User domain object
197 return keystone.users.get(user.id)
200 def get_user(keystone, username, project_name=None):
202 Returns a user for a given name and optionally project
203 :param keystone: the keystone client
204 :param username: the username to lookup
205 :param project_name: the associated project (optional)
206 :return: a SNAPS-OO User domain object or None
210 project = get_project(keystone=keystone, project_name=project_name)
213 users = keystone.users.list(tenant_id=project.id)
215 users = keystone.users.list()
218 if user.name == username:
219 return User(name=user.name, user_id=user.id)
224 def create_user(keystone, user_settings):
227 :param keystone: the Keystone client
228 :param user_settings: the user configuration
229 :return: a SNAPS-OO User domain object
232 if user_settings.project_name:
233 project = get_project(keystone=keystone,
234 project_name=user_settings.project_name)
236 if keystone.version == V2_VERSION_STR:
239 project_id = project.id
240 os_user = keystone.users.create(
241 name=user_settings.name, password=user_settings.password,
242 email=user_settings.email, tenant_id=project_id,
243 enabled=user_settings.enabled)
245 os_domain = __get_os_domain_by_name(
246 keystone, user_settings.domain_name)
248 os_domain = user_settings.domain_name
249 os_user = keystone.users.create(
250 name=user_settings.name, password=user_settings.password,
251 email=user_settings.email, project=project,
252 domain=os_domain, enabled=user_settings.enabled)
254 for role_name, role_project in user_settings.roles.items():
255 os_role = get_role_by_name(keystone, role_name)
256 os_project = get_project(keystone=keystone, project_name=role_project)
258 if os_role and os_project:
259 existing_roles = get_roles_by_user(keystone, os_user, os_project)
261 for role in existing_roles:
262 if role.id == os_role.id:
266 grant_user_role_to_project(
267 keystone=keystone, user=os_user, role=os_role,
271 return User(name=os_user.name, user_id=os_user.id)
274 def delete_user(keystone, user):
277 :param keystone: the Keystone client
278 :param user: the SNAPS-OO User domain object
280 keystone.users.delete(user.id)
283 def get_role_by_name(keystone, name):
285 Returns an OpenStack role object of a given name or None if not exists
286 :param keystone: the keystone client
287 :param name: the role name
288 :return: the SNAPS-OO Role domain object
290 roles = keystone.roles.list()
292 if role.name == name:
293 return Role(name=role.name, role_id=role.id)
296 def get_roles_by_user(keystone, user, project):
298 Returns a list of SNAPS-OO Role domain objects associated with a user
299 :param keystone: the keystone client
300 :param user: the OpenStack user object
301 :param project: the OpenStack project object (only required for v2)
302 :return: a list of SNAPS-OO Role domain objects
304 if keystone.version == V2_VERSION_STR:
305 os_user = __get_os_user(keystone, user)
306 roles = keystone.roles.roles_for_user(os_user, project)
308 roles = keystone.roles.list(user=user, project=project)
312 out.append(Role(name=role.name, role_id=role.id))
316 def get_role_by_id(keystone, role_id):
318 Returns an OpenStack role object of a given name or None if not exists
319 :param keystone: the keystone client
320 :param role_id: the role ID
321 :return: a SNAPS-OO Role domain object
323 role = keystone.roles.get(role_id)
324 return Role(name=role.name, role_id=role.id)
327 def create_role(keystone, name):
329 Creates an OpenStack role
330 :param keystone: the keystone client
331 :param name: the role name
332 :return: a SNAPS-OO Role domain object
334 role = keystone.roles.create(name)
335 return Role(name=role.name, role_id=role.id)
338 def delete_role(keystone, role):
340 Deletes an OpenStack role
341 :param keystone: the keystone client
342 :param role: the SNAPS-OO Role domain object to delete
345 keystone.roles.delete(role.id)
348 def grant_user_role_to_project(keystone, role, user, project):
350 Grants user and role to a project
351 :param keystone: the Keystone client
352 :param role: the SNAPS-OO Role domain object used to join a project/user
353 :param user: the user to add to the project (SNAPS-OO User Domain object
354 :param project: the project to which to add a user
358 os_role = get_role_by_id(keystone, role.id)
359 if keystone.version == V2_VERSION_STR:
360 keystone.roles.add_user_role(user, os_role, tenant=project)
362 keystone.roles.grant(os_role, user=user, project=project)
365 def get_domain_by_id(keystone, domain_id):
367 Returns the first OpenStack domain with the given name else None
368 :param keystone: the Keystone client
369 :param domain_id: the domain ID to retrieve
370 :return: the SNAPS-OO Domain domain object
372 domain = keystone.domains.get(domain_id)
374 return Domain(name=domain.name, domain_id=domain.id)
377 def __get_os_domain_by_name(keystone, domain_name):
379 Returns the first OpenStack domain with the given name else None
380 :param keystone: the Keystone client
381 :param domain_name: the domain name to lookup
382 :return: the OpenStack domain object
384 domains = keystone.domains.list(name=domain_name)
385 for domain in domains:
386 if domain.name == domain_name:
390 class KeystoneException(Exception):
392 Exception when calls to the Keystone client cannot be served properly