Closing keystone sessions after done with them.
[snaps.git] / snaps / openstack / utils / keystone_utils.py
index c671b18..52c86d8 100644 (file)
@@ -1,4 +1,4 @@
-# Copyright (c) 2016 Cable Television Laboratories, Inc. ("CableLabs")
+# Copyright (c) 2017 Cable Television Laboratories, Inc. ("CableLabs")
 #                    and others.  All rights reserved.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 #                    and others.  All rights reserved.
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
 # limitations under the License.
 import logging
 
 # limitations under the License.
 import logging
 
+import keystoneauth1
 from keystoneclient.client import Client
 from keystoneauth1.identity import v3, v2
 from keystoneauth1 import session
 import requests
 from keystoneclient.client import Client
 from keystoneauth1.identity import v3, v2
 from keystoneauth1 import session
 import requests
+from keystoneclient.exceptions import NotFound
 
 
-from snaps.domain.project import Project
+from snaps.domain.project import Project, Domain
 from snaps.domain.role import Role
 from snaps.domain.user import User
 
 logger = logging.getLogger('keystone_utils')
 
 from snaps.domain.role import Role
 from snaps.domain.user import User
 
 logger = logging.getLogger('keystone_utils')
 
-V2_VERSION = 'v2.0'
+V2_VERSION_NUM = 2.0
+V2_VERSION_STR = 'v' + str(V2_VERSION_NUM)
 
 
 def get_session_auth(os_creds):
 
 
 def get_session_auth(os_creds):
@@ -40,7 +43,9 @@ def get_session_auth(os_creds):
                            password=os_creds.password,
                            project_name=os_creds.project_name,
                            user_domain_id=os_creds.user_domain_id,
                            password=os_creds.password,
                            project_name=os_creds.project_name,
                            user_domain_id=os_creds.user_domain_id,
-                           project_domain_id=os_creds.project_domain_id)
+                           user_domain_name=os_creds.user_domain_name,
+                           project_domain_id=os_creds.project_domain_id,
+                           project_domain_name=os_creds.project_domain_name)
     else:
         auth = v2.Password(auth_url=os_creds.auth_url,
                            username=os_creds.username,
     else:
         auth = v2.Password(auth_url=os_creds.auth_url,
                            username=os_creds.username,
@@ -65,20 +70,40 @@ def keystone_session(os_creds):
         req_session.proxies = {
             'http':
                 os_creds.proxy_settings.host + ':' +
         req_session.proxies = {
             'http':
                 os_creds.proxy_settings.host + ':' +
-                os_creds.proxy_settings.port}
+                os_creds.proxy_settings.port,
+            'https':
+                os_creds.proxy_settings.https_host + ':' +
+                os_creds.proxy_settings.https_port
+        }
     return session.Session(auth=auth, session=req_session,
                            verify=os_creds.cacert)
 
 
     return session.Session(auth=auth, session=req_session,
                            verify=os_creds.cacert)
 
 
-def keystone_client(os_creds):
+def close_session(session):
+    """
+    Closes a keystone session
+    :param session: a session.Session object
+    """
+    if isinstance(session, keystoneauth1.session.Session):
+        session.session.close()
+
+
+def keystone_client(os_creds, session=None):
     """
     Returns the keystone client
     :param os_creds: the OpenStack credentials (OSCreds) object
     """
     Returns the keystone client
     :param os_creds: the OpenStack credentials (OSCreds) object
+    :param session: the keystone session object (optional)
     :return: the client
     """
     :return: the client
     """
+
+    if not session:
+        session = keystone_session(os_creds)
+
     return Client(
         version=os_creds.identity_api_version,
     return Client(
         version=os_creds.identity_api_version,
-        session=keystone_session(os_creds), interface=os_creds.interface)
+        session=session,
+        interface=os_creds.interface,
+        region_name=os_creds.region_name)
 
 
 def get_endpoint(os_creds, service_type, interface='public'):
 
 
 def get_endpoint(os_creds, service_type, interface='public'):
@@ -92,38 +117,65 @@ def get_endpoint(os_creds, service_type, interface='public'):
     auth = get_session_auth(os_creds)
     key_session = keystone_session(os_creds)
     return key_session.get_endpoint(
     auth = get_session_auth(os_creds)
     key_session = keystone_session(os_creds)
     return key_session.get_endpoint(
-        auth=auth, service_type=service_type, interface=interface)
+        auth=auth, service_type=service_type, region_name=os_creds.region_name,
+        interface=interface)
 
 
 
 
-def get_project(keystone=None, os_creds=None, project_name=None):
+def get_project(keystone=None, project_settings=None, project_name=None):
     """
     """
-    Returns the first project object or None if not found
+    Returns the first project where the project_settings is used for the query
+    if not None, else the project_name parameter is used for the query. If both
+    parameters are None, None is returned
     :param keystone: the Keystone client
     :param keystone: the Keystone client
-    :param os_creds: the OpenStack credentials used to obtain the Keystone
-                     client if the keystone parameter is None
+    :param project_settings: a ProjectConfig object
     :param project_name: the name to query
     :param project_name: the name to query
-    :return: the ID or None
+    :return: the SNAPS-OO Project domain object or None
     """
     """
-    if not project_name:
-        return None
+    proj_filter = dict()
 
 
-    if not keystone:
-        if os_creds:
-            keystone = keystone_client(os_creds)
-        else:
-            raise KeystoneException(
-                'Cannot lookup project without the proper credentials')
+    if project_name:
+        proj_filter['name'] = project_name
+    elif project_settings:
+        proj_filter['name'] = project_settings.name
+        proj_filter['description'] = project_settings.description
+        proj_filter['domain_name'] = project_settings.domain_name
+        proj_filter['enabled'] = project_settings.enabled
+    else:
+        return None
 
 
-    if keystone.version == V2_VERSION:
+    if keystone.version == V2_VERSION_STR:
         projects = keystone.tenants.list()
     else:
         projects = keystone.tenants.list()
     else:
-        projects = keystone.projects.list(**{'name': project_name})
+        projects = keystone.projects.list(**proj_filter)
 
     for project in projects:
 
     for project in projects:
-        if project.name == project_name:
-            return Project(name=project.name, project_id=project.id)
+        if project.name == proj_filter['name']:
+            domain_id = None
+            if keystone.version != V2_VERSION_STR:
+                domain_id = project.domain_id
 
 
-    return None
+            return Project(name=project.name, project_id=project.id,
+                           domain_id=domain_id)
+
+
+def get_project_by_id(keystone, proj_id):
+    """
+    Returns the first project where the project_settings is used for the query
+    if not None, else the project_name parameter is used for the query. If both
+    parameters are None, None is returned
+    :param keystone: the Keystone client
+    :param proj_id: the project ID
+    """
+    if proj_id and len(proj_id) > 0:
+        try:
+            os_proj = keystone.projects.get(proj_id)
+            if os_proj:
+                return Project(name=os_proj.name, project_id=os_proj.id,
+                               domain_id=os_proj)
+        except NotFound:
+            pass
+        except KeyError:
+            pass
 
 
 def create_project(keystone, project_settings):
 
 
 def create_project(keystone, project_settings):
@@ -133,15 +185,26 @@ def create_project(keystone, project_settings):
     :param project_settings: the project configuration
     :return: SNAPS-OO Project domain object
     """
     :param project_settings: the project configuration
     :return: SNAPS-OO Project domain object
     """
-    if keystone.version == V2_VERSION:
-        return keystone.tenants.create(
+    domain_id = None
+
+    if keystone.version == V2_VERSION_STR:
+        os_project = keystone.tenants.create(
             project_settings.name, project_settings.description,
             project_settings.enabled)
             project_settings.name, project_settings.description,
             project_settings.enabled)
+    else:
+        os_domain = __get_os_domain_by_name(
+            keystone, project_settings.domain_name)
+        if not os_domain:
+            os_domain = project_settings.domain_name
+        os_project = keystone.projects.create(
+            project_settings.name, os_domain,
+            description=project_settings.description,
+            enabled=project_settings.enabled)
+        domain_id = os_project.domain_id
 
 
-    return keystone.projects.create(
-        project_settings.name, project_settings.domain,
-        description=project_settings.description,
-        enabled=project_settings.enabled)
+    logger.info('Created project with name - %s', project_settings.name)
+    return Project(
+        name=os_project.name, project_id=os_project.id, domain_id=domain_id)
 
 
 def delete_project(keystone, project):
 
 
 def delete_project(keystone, project):
@@ -150,7 +213,8 @@ def delete_project(keystone, project):
     :param keystone: the Keystone clien
     :param project: the SNAPS-OO Project domain object
     """
     :param keystone: the Keystone clien
     :param project: the SNAPS-OO Project domain object
     """
-    if keystone.version == V2_VERSION:
+    logger.info('Deleting project with name - %s', project.name)
+    if keystone.version == V2_VERSION_STR:
         keystone.tenants.delete(project.id)
     else:
         keystone.projects.delete(project.id)
         keystone.tenants.delete(project.id)
     else:
         keystone.projects.delete(project.id)
@@ -174,7 +238,9 @@ def get_user(keystone, username, project_name=None):
     :param project_name: the associated project (optional)
     :return: a SNAPS-OO User domain object or None
     """
     :param project_name: the associated project (optional)
     :return: a SNAPS-OO User domain object or None
     """
-    project = get_project(keystone=keystone, project_name=project_name)
+    project = None
+    if project_name:
+        project = get_project(keystone=keystone, project_name=project_name)
 
     if project:
         users = keystone.users.list(tenant_id=project.id)
 
     if project:
         users = keystone.users.list(tenant_id=project.id)
@@ -197,10 +263,10 @@ def create_user(keystone, user_settings):
     """
     project = None
     if user_settings.project_name:
     """
     project = None
     if user_settings.project_name:
-        project = get_project(keystone=keystone,
-                              project_name=user_settings.project_name)
+        project = get_project(
+            keystone=keystone, project_name=user_settings.project_name)
 
 
-    if keystone.version == V2_VERSION:
+    if keystone.version == V2_VERSION_STR:
         project_id = None
         if project:
             project_id = project.id
         project_id = None
         if project:
             project_id = project.id
@@ -209,18 +275,21 @@ def create_user(keystone, user_settings):
             email=user_settings.email, tenant_id=project_id,
             enabled=user_settings.enabled)
     else:
             email=user_settings.email, tenant_id=project_id,
             enabled=user_settings.enabled)
     else:
+        os_domain = __get_os_domain_by_name(
+            keystone, user_settings.domain_name)
+        if not os_domain:
+            os_domain = user_settings.domain_name
         os_user = keystone.users.create(
             name=user_settings.name, password=user_settings.password,
             email=user_settings.email, project=project,
         os_user = keystone.users.create(
             name=user_settings.name, password=user_settings.password,
             email=user_settings.email, project=project,
-            domain=user_settings.domain_name, enabled=user_settings.enabled)
+            domain=os_domain, enabled=user_settings.enabled)
 
     for role_name, role_project in user_settings.roles.items():
 
     for role_name, role_project in user_settings.roles.items():
-        os_role = _get_os_role_by_name(keystone, role_name)
+        os_role = get_role_by_name(keystone, role_name)
         os_project = get_project(keystone=keystone, project_name=role_project)
 
         if os_role and os_project:
         os_project = get_project(keystone=keystone, project_name=role_project)
 
         if os_role and os_project:
-            existing_roles = _get_os_roles_by_user(keystone, os_user,
-                                                   os_project)
+            existing_roles = get_roles_by_user(keystone, os_user, os_project)
             found = False
             for role in existing_roles:
                 if role.id == os_role.id:
             found = False
             for role in existing_roles:
                 if role.id == os_role.id:
@@ -232,6 +301,7 @@ def create_user(keystone, user_settings):
                     project=os_project)
 
     if os_user:
                     project=os_project)
 
     if os_user:
+        logger.info('Created user with name - %s', os_user.name)
         return User(name=os_user.name, user_id=os_user.id)
 
 
         return User(name=os_user.name, user_id=os_user.id)
 
 
@@ -241,10 +311,11 @@ def delete_user(keystone, user):
     :param keystone: the Keystone client
     :param user: the SNAPS-OO User domain object
     """
     :param keystone: the Keystone client
     :param user: the SNAPS-OO User domain object
     """
+    logger.info('Deleting user with name - %s', user.name)
     keystone.users.delete(user.id)
 
 
     keystone.users.delete(user.id)
 
 
-def _get_os_role_by_name(keystone, name):
+def get_role_by_name(keystone, name):
     """
     Returns an OpenStack role object of a given name or None if not exists
     :param keystone: the keystone client
     """
     Returns an OpenStack role object of a given name or None if not exists
     :param keystone: the keystone client
@@ -257,15 +328,15 @@ def _get_os_role_by_name(keystone, name):
             return Role(name=role.name, role_id=role.id)
 
 
             return Role(name=role.name, role_id=role.id)
 
 
-def _get_os_roles_by_user(keystone, user, project):
+def get_roles_by_user(keystone, user, project):
     """
     """
-    Returns a list of OpenStack role object associated with a user
+    Returns a list of SNAPS-OO Role domain objects associated with a user
     :param keystone: the keystone client
     :param user: the OpenStack user object
     :param project: the OpenStack project object (only required for v2)
     :return: a list of SNAPS-OO Role domain objects
     """
     :param keystone: the keystone client
     :param user: the OpenStack user object
     :param project: the OpenStack project object (only required for v2)
     :return: a list of SNAPS-OO Role domain objects
     """
-    if keystone.version == V2_VERSION:
+    if keystone.version == V2_VERSION_STR:
         os_user = __get_os_user(keystone, user)
         roles = keystone.roles.roles_for_user(os_user, project)
     else:
         os_user = __get_os_user(keystone, user)
         roles = keystone.roles.roles_for_user(os_user, project)
     else:
@@ -277,7 +348,7 @@ def _get_os_roles_by_user(keystone, user, project):
     return out
 
 
     return out
 
 
-def __get_os_role_by_id(keystone, role_id):
+def get_role_by_id(keystone, role_id):
     """
     Returns an OpenStack role object of a given name or None if not exists
     :param keystone: the keystone client
     """
     Returns an OpenStack role object of a given name or None if not exists
     :param keystone: the keystone client
@@ -296,6 +367,7 @@ def create_role(keystone, name):
     :return: a SNAPS-OO Role domain object
     """
     role = keystone.roles.create(name)
     :return: a SNAPS-OO Role domain object
     """
     role = keystone.roles.create(name)
+    logger.info('Created role with name - %s', role.name)
     return Role(name=role.name, role_id=role.id)
 
 
     return Role(name=role.name, role_id=role.id)
 
 
@@ -306,6 +378,7 @@ def delete_role(keystone, role):
     :param role: the SNAPS-OO Role domain object to delete
     :return:
     """
     :param role: the SNAPS-OO Role domain object to delete
     :return:
     """
+    logger.info('Deleting role with name - %s', role.name)
     keystone.roles.delete(role.id)
 
 
     keystone.roles.delete(role.id)
 
 
@@ -319,13 +392,40 @@ def grant_user_role_to_project(keystone, role, user, project):
     :return:
     """
 
     :return:
     """
 
-    os_role = __get_os_role_by_id(keystone, role.id)
-    if keystone.version == V2_VERSION:
+    os_role = get_role_by_id(keystone, role.id)
+    logger.info('Granting role %s to project %s', role.name, project.name)
+    if keystone.version == V2_VERSION_STR:
         keystone.roles.add_user_role(user, os_role, tenant=project)
     else:
         keystone.roles.grant(os_role, user=user, project=project)
 
 
         keystone.roles.add_user_role(user, os_role, tenant=project)
     else:
         keystone.roles.grant(os_role, user=user, project=project)
 
 
+def get_domain_by_id(keystone, domain_id):
+    """
+    Returns the first OpenStack domain with the given name else None
+    :param keystone: the Keystone client
+    :param domain_id: the domain ID to retrieve
+    :return: the SNAPS-OO Domain domain object
+    """
+    if keystone.version != V2_VERSION_STR:
+        domain = keystone.domains.get(domain_id)
+        if domain:
+            return Domain(name=domain.name, domain_id=domain.id)
+
+
+def __get_os_domain_by_name(keystone, domain_name):
+    """
+    Returns the first OpenStack domain with the given name else None
+    :param keystone: the Keystone client
+    :param domain_name: the domain name to lookup
+    :return: the OpenStack domain object
+    """
+    domains = keystone.domains.list(name=domain_name)
+    for domain in domains:
+        if domain.name == domain_name:
+            return domain
+
+
 class KeystoneException(Exception):
     """
     Exception when calls to the Keystone client cannot be served properly
 class KeystoneException(Exception):
     """
     Exception when calls to the Keystone client cannot be served properly