Add domain name when creating projects and users
[snaps.git] / snaps / openstack / utils / keystone_utils.py
1 # Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
2 #                    and others.  All rights reserved.
3 #
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:
7 #
8 #     http://www.apache.org/licenses/LICENSE-2.0
9 #
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.
15 import logging
16
17 from keystoneclient.client import Client
18 from keystoneauth1.identity import v3, v2
19 from keystoneauth1 import session
20 import requests
21
22 from snaps.domain.project import Project, Domain
23 from snaps.domain.role import Role
24 from snaps.domain.user import User
25
26 logger = logging.getLogger('keystone_utils')
27
28 V2_VERSION_NUM = 2.0
29 V2_VERSION_STR = 'v' + str(V2_VERSION_NUM)
30
31
32 def get_session_auth(os_creds):
33     """
34     Return the session auth for keystone session
35     :param os_creds: the OpenStack credentials (OSCreds) object
36     :return: the auth
37     """
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)
47     else:
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)
52     return auth
53
54
55 def keystone_session(os_creds):
56     """
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
60     """
61     logger.debug('Retrieving Keystone Session')
62
63     auth = get_session_auth(os_creds)
64
65     req_session = None
66     if os_creds.proxy_settings:
67         req_session = requests.Session()
68         req_session.proxies = {
69             'http':
70                 os_creds.proxy_settings.host + ':' +
71                 os_creds.proxy_settings.port,
72             'https':
73                 os_creds.proxy_settings.https_host + ':' +
74                 os_creds.proxy_settings.https_port
75         }
76     return session.Session(auth=auth, session=req_session,
77                            verify=os_creds.cacert)
78
79
80 def keystone_client(os_creds):
81     """
82     Returns the keystone client
83     :param os_creds: the OpenStack credentials (OSCreds) object
84     :return: the client
85     """
86     return Client(
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)
91
92
93 def get_endpoint(os_creds, service_type, interface='public'):
94     """
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
100     """
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)
105
106
107 def get_project(keystone=None, os_creds=None, project_settings=None,
108                 project_name=None):
109     """
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
117     """
118     if not keystone:
119         if os_creds:
120             keystone = keystone_client(os_creds)
121         else:
122             raise KeystoneException(
123                 'Cannot lookup project without the proper credentials')
124
125     proj_filter = dict()
126
127     if project_name:
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
134
135     if keystone.version == V2_VERSION_STR:
136         projects = keystone.tenants.list()
137     else:
138         projects = keystone.projects.list(**proj_filter)
139
140     for project in projects:
141         if project.name == proj_filter['name']:
142             domain_id = None
143             if keystone.version != V2_VERSION_STR:
144                 domain_id = project.domain_id
145
146             return Project(name=project.name, project_id=project.id,
147                            domain_id=domain_id)
148
149
150 def create_project(keystone, project_settings):
151     """
152     Creates a project
153     :param keystone: the Keystone client
154     :param project_settings: the project configuration
155     :return: SNAPS-OO Project domain object
156     """
157     domain_id = None
158
159     if keystone.version == V2_VERSION_STR:
160         os_project = keystone.tenants.create(
161             project_settings.name, project_settings.description,
162             project_settings.enabled)
163     else:
164         os_domain = __get_os_domain_by_name(
165             keystone, project_settings.domain_name)
166         if not os_domain:
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
173
174     return Project(
175         name=os_project.name, project_id=os_project.id, domain_id=domain_id)
176
177
178 def delete_project(keystone, project):
179     """
180     Deletes a project
181     :param keystone: the Keystone clien
182     :param project: the SNAPS-OO Project domain object
183     """
184     if keystone.version == V2_VERSION_STR:
185         keystone.tenants.delete(project.id)
186     else:
187         keystone.projects.delete(project.id)
188
189
190 def __get_os_user(keystone, user):
191     """
192     Returns the OpenStack user object
193     :param keystone: the Keystone client object
194     :param user: the SNAPS-OO User domain object
195     :return:
196     """
197     return keystone.users.get(user.id)
198
199
200 def get_user(keystone, username, project_name=None):
201     """
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
207     """
208     project = None
209     if project_name:
210         project = get_project(keystone=keystone, project_name=project_name)
211
212     if project:
213         users = keystone.users.list(tenant_id=project.id)
214     else:
215         users = keystone.users.list()
216
217     for user in users:
218         if user.name == username:
219             return User(name=user.name, user_id=user.id)
220
221     return None
222
223
224 def create_user(keystone, user_settings):
225     """
226     Creates a user
227     :param keystone: the Keystone client
228     :param user_settings: the user configuration
229     :return: a SNAPS-OO User domain object
230     """
231     project = None
232     if user_settings.project_name:
233         project = get_project(keystone=keystone,
234                               project_name=user_settings.project_name)
235
236     if keystone.version == V2_VERSION_STR:
237         project_id = None
238         if project:
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)
244     else:
245         os_domain = __get_os_domain_by_name(
246             keystone, user_settings.domain_name)
247         if not os_domain:
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)
253
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)
257
258         if os_role and os_project:
259             existing_roles = get_roles_by_user(keystone, os_user, os_project)
260             found = False
261             for role in existing_roles:
262                 if role.id == os_role.id:
263                     found = True
264
265             if not found:
266                 grant_user_role_to_project(
267                     keystone=keystone, user=os_user, role=os_role,
268                     project=os_project)
269
270     if os_user:
271         return User(name=os_user.name, user_id=os_user.id)
272
273
274 def delete_user(keystone, user):
275     """
276     Deletes a user
277     :param keystone: the Keystone client
278     :param user: the SNAPS-OO User domain object
279     """
280     keystone.users.delete(user.id)
281
282
283 def get_role_by_name(keystone, name):
284     """
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
289     """
290     roles = keystone.roles.list()
291     for role in roles:
292         if role.name == name:
293             return Role(name=role.name, role_id=role.id)
294
295
296 def get_roles_by_user(keystone, user, project):
297     """
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
303     """
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)
307     else:
308         roles = keystone.roles.list(user=user, project=project)
309
310     out = list()
311     for role in roles:
312         out.append(Role(name=role.name, role_id=role.id))
313     return out
314
315
316 def get_role_by_id(keystone, role_id):
317     """
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
322     """
323     role = keystone.roles.get(role_id)
324     return Role(name=role.name, role_id=role.id)
325
326
327 def create_role(keystone, name):
328     """
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
333     """
334     role = keystone.roles.create(name)
335     return Role(name=role.name, role_id=role.id)
336
337
338 def delete_role(keystone, role):
339     """
340     Deletes an OpenStack role
341     :param keystone: the keystone client
342     :param role: the SNAPS-OO Role domain object to delete
343     :return:
344     """
345     keystone.roles.delete(role.id)
346
347
348 def grant_user_role_to_project(keystone, role, user, project):
349     """
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
355     :return:
356     """
357
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)
361     else:
362         keystone.roles.grant(os_role, user=user, project=project)
363
364
365 def get_domain_by_id(keystone, domain_id):
366     """
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
371     """
372     domain = keystone.domains.get(domain_id)
373     if domain:
374         return Domain(name=domain.name, domain_id=domain.id)
375
376
377 def __get_os_domain_by_name(keystone, domain_name):
378     """
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
383     """
384     domains = keystone.domains.list(name=domain_name)
385     for domain in domains:
386         if domain.name == domain_name:
387             return domain
388
389
390 class KeystoneException(Exception):
391     """
392     Exception when calls to the Keystone client cannot be served properly
393     """