Merge "Add keystoneng module"
diff --git a/_modules/keystoneng.py b/_modules/keystoneng.py
new file mode 100644
index 0000000..66e0ac7
--- /dev/null
+++ b/_modules/keystoneng.py
@@ -0,0 +1,1418 @@
+# -*- coding: utf-8 -*-
+'''
+Module for handling openstack keystone calls.
+
+:optdepends:    - keystoneclient Python adapter
+:configuration: This module is not usable until the following are specified
+    either in a pillar or in the minion's config file:
+
+    .. code-block:: yaml
+
+        keystone.user: admin
+        keystone.password: verybadpass
+        keystone.tenant: admin
+        keystone.tenant_id: f80919baedab48ec8931f200c65a50df
+        keystone.auth_url: 'http://127.0.0.1:5000/v2.0/'
+
+    OR (for token based authentication)
+
+    .. code-block:: yaml
+
+        keystone.token: 'ADMIN'
+        keystone.endpoint: 'http://127.0.0.1:35357/v2.0'
+
+    If configuration for multiple openstack accounts is required, they can be
+    set up as different configuration profiles. For example:
+
+    .. code-block:: yaml
+
+        openstack1:
+          keystone.user: admin
+          keystone.password: verybadpass
+          keystone.tenant: admin
+          keystone.tenant_id: f80919baedab48ec8931f200c65a50df
+          keystone.auth_url: 'http://127.0.0.1:5000/v2.0/'
+
+        openstack2:
+          keystone.user: admin
+          keystone.password: verybadpass
+          keystone.tenant: admin
+          keystone.tenant_id: f80919baedab48ec8931f200c65a50df
+          keystone.auth_url: 'http://127.0.0.2:5000/v2.0/'
+
+        openstack_version3:
+          keystone.user: admin
+          keystone.password: verybadpass
+          keystone.tenant: admin
+          keystone.tenant_id: f80919baedab48ec8931f200c65a50df
+          keystone.auth_url: 'http://127.0.0.2:5000/v3'
+
+        openstack_nonversioned:
+          keystone.user: admin
+          keystone.password: verybadpass
+          keystone.tenant: admin
+          keystone.tenant_id: f80919baedab48ec8931f200c65a50df
+          keystone.auth_url: 'http://127.0.0.2:5000'
+
+    With this configuration in place, any of the keystone functions can make use
+    of a configuration profile by declaring it explicitly.
+    For example:
+
+    .. code-block:: bash
+
+        salt '*' keystone.tenant_list profile=openstack1
+'''
+
+# Import Python libs
+from __future__ import absolute_import
+import logging
+
+# Import Salt Libs
+import salt.utils.http
+
+# Import 3rd-party libs
+from salt.ext import six
+HAS_KEYSTONE = False
+try:
+    # pylint: disable=import-error
+    from keystoneclient import client
+    from keystoneclient.v2_0 import client as keystoneclient_v2
+    import keystoneclient.exceptions
+    HAS_KEYSTONE = True
+    from keystoneclient.v3 import client as keystoneclient_v3
+    from keystoneclient import discover
+    from keystoneauth1 import session
+    from keystoneauth1.identity import generic
+    from keystoneauth1 import token_endpoint
+    # pylint: enable=import-error
+except ImportError:
+    pass
+
+log = logging.getLogger(__name__)
+
+
+def __virtual__():
+    '''
+    Only load this module if keystone
+    is installed on this minion.
+    '''
+    if HAS_KEYSTONE:
+        return 'keystoneng'
+    return (False, 'keystone execution module cannot be loaded: keystoneclient python library not available.')
+
+__opts__ = {}
+
+
+def _get_kwargs(profile=None, **connection_args):
+    '''
+    get connection args
+    '''
+    if profile:
+        prefix = profile + ":keystone."
+    else:
+        prefix = "keystone."
+
+    def get(key, default=None):
+        '''
+        look in connection_args first, then default to config file
+        '''
+        return connection_args.get('connection_' + key,
+                                   __salt__['config.get'](prefix + key, default))
+
+    user = get('user', 'admin')
+    password = get('password', 'ADMIN')
+    tenant = get('tenant', 'admin')
+    tenant_id = get('tenant_id')
+    auth_url = get('auth_url', 'http://127.0.0.1:5000/v2.0/')
+    insecure = get('insecure', False)
+    token = get('token')
+    endpoint = get('endpoint', 'http://127.0.0.1:5000/v2.0')
+    if token:
+        kwargs = {'token': token,
+                  'endpoint': endpoint}
+    else:
+        kwargs = {'username': user,
+                  'password': password,
+                  'tenant_name': tenant,
+                  'tenant_id': tenant_id,
+                  'auth_url': auth_url}
+        # 'insecure' keyword not supported by all v2.0 keystone clients
+        #   this ensures it's only passed in when defineda
+        if get('user_id'): kwargs['user_id']=get('user_id')
+        if get('user_domain_id'): kwargs['user_domain_id']=get('user_domain_id')
+        if get('domain_id'): kwargs['domain_id']=get('domain_id')
+        if get('domain_name'): kwargs['domain_name']=get('domain_name')
+        if get('project_id'): kwargs['project_id']=get('project_id')
+        if get('project_name'): kwargs['project_name']=get('project_name')
+        if get('project_domain_id'): kwargs['project_domain_id']=get('project_domain_id')
+        if get('region_name'): kwargs['region_name']=get('region_name')
+        if get('endpoint'): kwargs['endpoint']=get('endpoint')
+        if get('timeout'): kwargs['timeout']=get('timeout')
+        if get('user_domain_name'): kwargs['user_domain_name']=get('user_domain_name')
+        if get('project_domain_name'): kwargs['project_domain_name']=get('project_domain_name')
+        kwargs['insecure'] = get('insecure', False)
+        if get('version'):
+            version_list = str(get('version')).split('.')
+            if len(version_list) == 2:
+                kwargs['version'] = (version_list[0], version_list[1])
+            else:
+                kwargs['version'] = (version_list[0])
+
+    return kwargs
+
+
+def _client_version(keystone_client):
+    '''
+    Returns keystone client version
+    '''
+    if isinstance(keystone_client, keystoneclient_v3.Client):
+        return 3
+    if isinstance(keystone_client, keystoneclient_v2.Client):
+        return 2
+    return None
+
+
+def _project_mgr(keystone_client):
+    '''
+    Returns keystoneclient.v3.Client.projects object if keystone client version > 2
+    or keystoneclient.v2_0.Client.tenants for other cases.
+    '''
+    if _client_version(keystone_client) > 2:
+        return keystone_client.projects
+    return keystone_client.tenants
+
+
+def api_version(profile=None, **connection_args):
+    '''
+    Returns the API version derived from endpoint's response.
+
+    CLI Example:
+
+    .. code-block:: bash
+
+        salt '*' keystone.api_version
+    '''
+    keystone_client = auth(profile, **connection_args)
+    if isinstance(keystone_client, keystoneclient_v3.Client):
+        return 3
+    if isinstance(keystone_client, keystoneclient_v2.Client):
+        return 2
+    return None
+
+
+def auth(profile=None, **connection_args):
+    '''
+    Set up keystone credentials. Only intended to be used within Keystone-enabled modules.
+
+    CLI Example:
+
+    .. code-block:: bash
+
+        salt '*' keystone.auth
+    '''
+    kwargs = _get_kwargs(profile=profile, **connection_args)
+    if 'token' in kwargs:
+        auth = token_endpoint.Token(endpoint=kwargs['endpoint'], token=kwargs['token'])
+    else:
+        # keystoneauth1 Password class does not accept some args. Therefore remove it from args for auth.
+        auth_connection_args=kwargs.copy()
+        auth_connection_args.pop('region_name', None)
+        auth_connection_args.pop('version', None)
+        auth_connection_args.pop('insecure', None)
+        auth = generic.Password(**auth_connection_args)
+    if 'insecure' in kwargs:
+         certs_verify = False
+    else:
+        certs_verify = True
+    sess = session.Session(auth=auth, verify=certs_verify)
+    keystone_client=client.Client(session=sess, **kwargs)
+    return keystone_client
+
+
+def ec2_credentials_create(user_id=None, name=None,
+                           tenant_id=None, tenant=None,
+                           profile=None, **connection_args):
+    '''
+    Create EC2-compatible credentials for user per tenant
+
+    CLI Examples:
+
+    .. code-block:: bash
+
+        salt '*' keystone.ec2_credentials_create name=admin tenant=admin
+
+        salt '*' keystone.ec2_credentials_create \
+        user_id=c965f79c4f864eaaa9c3b41904e67082 \
+        tenant_id=722787eb540849158668370dc627ec5f
+    '''
+    kstone = auth(profile, **connection_args)
+
+    if name:
+        user_id = user_get(name=name, profile=profile,
+                           **connection_args)[name]['id']
+    if not user_id:
+        return {'Error': 'Could not resolve User ID'}
+
+    if tenant:
+        tenant_id = tenant_get(name=tenant, profile=profile,
+                               **connection_args)[tenant]['id']
+    if not tenant_id:
+        return {'Error': 'Could not resolve Tenant ID'}
+
+    newec2 = kstone.ec2.create(user_id, tenant_id)
+    return {'access': newec2.access,
+            'secret': newec2.secret,
+            'tenant_id': newec2.tenant_id,
+            'user_id': newec2.user_id}
+
+
+def ec2_credentials_delete(user_id=None, name=None, access_key=None,
+                           profile=None, **connection_args):
+    '''
+    Delete EC2-compatible credentials
+
+    CLI Examples:
+
+    .. code-block:: bash
+
+        salt '*' keystone.ec2_credentials_delete \
+        860f8c2c38ca4fab989f9bc56a061a64 access_key=5f66d2f24f604b8bb9cd28886106f442
+
+        salt '*' keystone.ec2_credentials_delete name=admin \
+        access_key=5f66d2f24f604b8bb9cd28886106f442
+    '''
+    kstone = auth(profile, **connection_args)
+
+    if name:
+        user_id = user_get(name=name, profile=None, **connection_args)[name]['id']
+    if not user_id:
+        return {'Error': 'Could not resolve User ID'}
+    kstone.ec2.delete(user_id, access_key)
+    return 'ec2 key "{0}" deleted under user id "{1}"'.format(access_key,
+                                                              user_id)
+
+
+def ec2_credentials_get(user_id=None, name=None, access=None,
+                        profile=None, **connection_args):
+    '''
+    Return ec2_credentials for a user (keystone ec2-credentials-get)
+
+    CLI Examples:
+
+    .. code-block:: bash
+
+        salt '*' keystone.ec2_credentials_get c965f79c4f864eaaa9c3b41904e67082 access=722787eb540849158668370
+        salt '*' keystone.ec2_credentials_get user_id=c965f79c4f864eaaa9c3b41904e67082 access=722787eb540849158668370
+        salt '*' keystone.ec2_credentials_get name=nova access=722787eb540849158668370dc627ec5f
+    '''
+    kstone = auth(profile, **connection_args)
+    ret = {}
+    if name:
+        for user in kstone.users.list():
+            if user.name == name:
+                user_id = user.id
+                break
+    if not user_id:
+        return {'Error': 'Unable to resolve user id'}
+    if not access:
+        return {'Error': 'Access key is required'}
+    ec2_credentials = kstone.ec2.get(user_id=user_id, access=access,
+                                     profile=profile, **connection_args)
+    ret[ec2_credentials.user_id] = {'user_id': ec2_credentials.user_id,
+                                    'tenant': ec2_credentials.tenant_id,
+                                    'access': ec2_credentials.access,
+                                    'secret': ec2_credentials.secret}
+    return ret
+
+
+def ec2_credentials_list(user_id=None, name=None, profile=None,
+                         **connection_args):
+    '''
+    Return a list of ec2_credentials for a specific user (keystone ec2-credentials-list)
+
+    CLI Examples:
+
+    .. code-block:: bash
+
+        salt '*' keystone.ec2_credentials_list 298ce377245c4ec9b70e1c639c89e654
+        salt '*' keystone.ec2_credentials_list user_id=298ce377245c4ec9b70e1c639c89e654
+        salt '*' keystone.ec2_credentials_list name=jack
+    '''
+    kstone = auth(profile, **connection_args)
+    ret = {}
+    if name:
+        for user in kstone.users.list():
+            if user.name == name:
+                user_id = user.id
+                break
+    if not user_id:
+        return {'Error': 'Unable to resolve user id'}
+    for ec2_credential in kstone.ec2.list(user_id):
+        ret[ec2_credential.user_id] = {'user_id': ec2_credential.user_id,
+                                       'tenant_id': ec2_credential.tenant_id,
+                                       'access': ec2_credential.access,
+                                       'secret': ec2_credential.secret}
+    return ret
+
+
+def endpoint_get(service, region=None, profile=None, interface=None, **connection_args):
+    '''
+    Return a specific endpoint (keystone endpoint-get)
+
+    CLI Example:
+
+    .. code-block:: bash
+
+        salt 'v2' keystone.endpoint_get nova [region=RegionOne]
+
+        salt 'v3' keystone.endpoint_get nova interface=admin [region=RegionOne]
+    '''
+    auth(profile, **connection_args)
+    services = service_list(profile, **connection_args)
+    if service not in services:
+        return {'Error': 'Could not find the specified service'}
+    service_id = services[service]['id']
+    endpoints = endpoint_list(profile, **connection_args)
+
+    e = [_f for _f in [e
+          if e['service_id'] == service_id and
+            (e['region'] == region if region else True) and
+            (e['interface'] == interface if interface else True)
+            else None for e in endpoints.values()] if _f]
+    if len(e) > 1:
+        return {'Error': 'Multiple endpoints found ({0}) for the {1} service. Please specify region.'.format(e, service)}
+    if len(e) == 1:
+        return e[0]
+    return {'Error': 'Could not find endpoint for the specified service'}
+
+
+def endpoint_list(profile=None, **connection_args):
+    '''
+    Return a list of available endpoints (keystone endpoints-list)
+
+    CLI Example:
+
+    .. code-block:: bash
+
+        salt '*' keystone.endpoint_list
+    '''
+    kstone = auth(profile, **connection_args)
+    ret = {}
+
+    for endpoint in kstone.endpoints.list():
+        ret[endpoint.id] = dict((value, getattr(endpoint, value)) for value in dir(endpoint)
+                                if not value.startswith('_') and
+                                isinstance(getattr(endpoint, value), (six.string_types, dict, bool)))
+    return ret
+
+
+def endpoint_create(service, publicurl=None, internalurl=None, adminurl=None,
+                    region=None, profile=None, url=None, interface=None, **connection_args):
+    '''
+    Create an endpoint for an Openstack service
+
+    CLI Examples:
+
+    .. code-block:: bash
+
+        salt 'v2' keystone.endpoint_create nova 'http://public/url' 'http://internal/url' 'http://adminurl/url' region
+
+        salt 'v3' keystone.endpoint_create nova url='http://public/url' interface='public' region='RegionOne'
+    '''
+    kstone = auth(profile, **connection_args)
+    keystone_service = service_get(name=service, profile=profile,
+                                   **connection_args)
+    if not keystone_service or 'Error' in keystone_service:
+        return {'Error': 'Could not find the specified service'}
+
+    if _client_version(kstone) > 2:
+        kstone.endpoints.create(service=keystone_service[service]['id'],
+                                region_id=region,
+                                url=url,
+                                interface=interface)
+    else:
+        kstone.endpoints.create(region=region,
+                                service_id=keystone_service[service]['id'],
+                                publicurl=publicurl,
+                                adminurl=adminurl,
+                                internalurl=internalurl)
+    return endpoint_get(service, region, profile, interface, **connection_args)
+
+
+def endpoint_delete(service, region=None, profile=None, interface=None, **connection_args):
+    '''
+    Delete endpoints of an Openstack service
+
+    CLI Examples:
+
+    .. code-block:: bash
+
+        salt 'v2' keystone.endpoint_delete nova [region=RegionOne]
+
+        salt 'v3' keystone.endpoint_delete nova interface=admin [region=RegionOne]
+    '''
+    kstone = auth(profile, **connection_args)
+    endpoint = endpoint_get(service, region, profile, interface, **connection_args)
+    if not endpoint or 'Error' in endpoint:
+        return {'Error': 'Could not find any endpoints for the service'}
+    kstone.endpoints.delete(endpoint['id'])
+    endpoint = endpoint_get(service, region, profile, interface, **connection_args)
+    if not endpoint or 'Error' in endpoint:
+        return True
+
+
+def role_create(name, profile=None, **connection_args):
+    '''
+    Create a named role.
+
+    CLI Example:
+
+    .. code-block:: bash
+
+        salt '*' keystone.role_create admin
+    '''
+
+    kstone = auth(profile, **connection_args)
+    if 'Error' not in role_get(name=name, profile=profile, **connection_args):
+        return {'Error': 'Role "{0}" already exists'.format(name)}
+    kstone.roles.create(name)
+    return role_get(name=name, profile=profile, **connection_args)
+
+
+def role_delete(role_id=None, name=None, profile=None,
+                **connection_args):
+    '''
+    Delete a role (keystone role-delete)
+
+
+    CLI Examples:
+
+    .. code-block:: bash
+
+        salt '*' keystone.role_delete c965f79c4f864eaaa9c3b41904e67082
+        salt '*' keystone.role_delete role_id=c965f79c4f864eaaa9c3b41904e67082
+        salt '*' keystone.role_delete name=admin
+    '''
+    kstone = auth(profile, **connection_args)
+
+    if name:
+        for role in kstone.roles.list():
+            if role.name == name:
+                role_id = role.id
+                break
+    if not role_id:
+        return {'Error': 'Unable to resolve role id'}
+
+    role = kstone.roles.get(role_id)
+    kstone.roles.delete(role)
+
+    ret = 'Role ID {0} deleted'.format(role_id)
+    if name:
+        ret += ' ({0})'.format(name)
+    return ret
+
+
+def role_get(role_id=None, name=None, profile=None, **connection_args):
+    '''
+    Return a specific roles (keystone role-get)
+
+    CLI Examples:
+
+    .. code-block:: bash
+
+        salt '*' keystone.role_get c965f79c4f864eaaa9c3b41904e67082
+        salt '*' keystone.role_get role_id=c965f79c4f864eaaa9c3b41904e67082
+        salt '*' keystone.role_get name=nova
+    '''
+    kstone = auth(profile, **connection_args)
+    ret = {}
+    if name:
+        for role in kstone.roles.list():
+            if role.name == name:
+                role_id = role.id
+                break
+    if not role_id:
+        return {'Error': 'Unable to resolve role id'}
+    role = kstone.roles.get(role_id)
+
+    ret[role.name] = {'id': role.id,
+                      'name': role.name}
+    return ret
+
+
+def role_list(profile=None, **connection_args):
+    '''
+    Return a list of available roles (keystone role-list)
+
+    CLI Example:
+
+    .. code-block:: bash
+
+        salt '*' keystone.role_list
+    '''
+    kstone = auth(profile, **connection_args)
+    ret = {}
+    for role in kstone.roles.list():
+        ret[role.name] = dict((value, getattr(role, value)) for value in dir(role)
+                              if not value.startswith('_') and
+                              isinstance(getattr(role, value), (six.string_types, dict, bool)))
+    return ret
+
+
+def service_create(name, service_type, description=None, profile=None,
+                   **connection_args):
+    '''
+    Add service to Keystone service catalog
+
+    CLI Examples:
+
+    .. code-block:: bash
+
+        salt '*' keystone.service_create nova compute \
+'OpenStack Compute Service'
+    '''
+    kstone = auth(profile, **connection_args)
+    service = kstone.services.create(name, service_type, description=description)
+    return service_get(service.id, profile=profile, **connection_args)
+
+
+def service_delete(service_id=None, name=None, profile=None, **connection_args):
+    '''
+    Delete a service from Keystone service catalog
+
+    CLI Examples:
+
+    .. code-block:: bash
+
+        salt '*' keystone.service_delete c965f79c4f864eaaa9c3b41904e67082
+        salt '*' keystone.service_delete name=nova
+    '''
+    kstone = auth(profile, **connection_args)
+    if name:
+        service_id = service_get(name=name, profile=profile,
+                                 **connection_args)[name]['id']
+    kstone.services.delete(service_id)
+    return 'Keystone service ID "{0}" deleted'.format(service_id)
+
+
+def service_get(service_id=None, name=None, profile=None, **connection_args):
+    '''
+    Return a specific services (keystone service-get)
+
+    CLI Examples:
+
+    .. code-block:: bash
+
+        salt '*' keystone.service_get c965f79c4f864eaaa9c3b41904e67082
+        salt '*' keystone.service_get service_id=c965f79c4f864eaaa9c3b41904e67082
+        salt '*' keystone.service_get name=nova
+    '''
+    kstone = auth(profile, **connection_args)
+    ret = {}
+    if name:
+        for service in kstone.services.list():
+            if service.name == name:
+                service_id = service.id
+                break
+    if not service_id:
+        return {'Error': 'Unable to resolve service id'}
+    service = kstone.services.get(service_id)
+    ret[service.name] = dict((value, getattr(service, value)) for value in dir(service)
+                             if not value.startswith('_') and
+                             isinstance(getattr(service, value), (six.string_types, dict, bool)))
+    return ret
+
+
+def service_list(profile=None, **connection_args):
+    '''
+    Return a list of available services (keystone services-list)
+
+    CLI Example:
+
+    .. code-block:: bash
+
+        salt '*' keystone.service_list
+    '''
+    kstone = auth(profile, **connection_args)
+    ret = {}
+    for service in kstone.services.list():
+        ret[service.name] = dict((value, getattr(service, value)) for value in dir(service)
+                                 if not value.startswith('_') and
+                                 isinstance(getattr(service, value), (six.string_types, dict, bool)))
+    return ret
+
+
+def tenant_create(name, description=None, enabled=True, profile=None,
+                  **connection_args):
+    '''
+    Create a keystone tenant
+
+    CLI Examples:
+
+    .. code-block:: bash
+
+        salt '*' keystone.tenant_create nova description='nova tenant'
+        salt '*' keystone.tenant_create test enabled=False
+    '''
+    kstone = auth(profile, **connection_args)
+    new = _project_mgr(kstone).create(name, description, enabled)
+    return tenant_get(new.id, profile=profile, **connection_args)
+
+
+def project_create(name, domain, description=None, enabled=True, profile=None,
+                   **connection_args):
+    '''
+    Create a keystone project.
+    Overrides keystone tenant_create form api V2. For keystone api V3.
+
+    .. versionadded:: 2016.11.0
+
+    name
+        The project name, which must be unique within the owning domain.
+
+    domain
+        The domain name.
+
+    description
+        The project description.
+
+    enabled
+        Enables or disables the project.
+
+    profile
+        Configuration profile - if configuration for multiple openstack accounts required.
+
+    CLI Examples:
+
+    .. code-block:: bash
+
+        salt '*' keystone.project_create nova default description='Nova Compute Project'
+        salt '*' keystone.project_create test default enabled=False
+    '''
+    kstone = auth(profile, **connection_args)
+    new = _project_mgr(kstone).create(name=name, domain=domain,
+                                                 description=description, enabled=enabled)
+    return tenant_get(new.id, profile=profile, **connection_args)
+
+
+def tenant_delete(tenant_id=None, name=None, profile=None, **connection_args):
+    '''
+    Delete a tenant (keystone tenant-delete)
+
+    CLI Examples:
+
+    .. code-block:: bash
+
+        salt '*' keystone.tenant_delete c965f79c4f864eaaa9c3b41904e67082
+        salt '*' keystone.tenant_delete tenant_id=c965f79c4f864eaaa9c3b41904e67082
+        salt '*' keystone.tenant_delete name=demo
+    '''
+    kstone = auth(profile, **connection_args)
+    if name:
+        for tenant in _project_mgr(kstone).list():
+            if tenant.name == name:
+                tenant_id = tenant.id
+                break
+    if not tenant_id:
+        return {'Error': 'Unable to resolve tenant id'}
+    _project_mgr(kstone).delete(tenant_id)
+    ret = 'Tenant ID {0} deleted'.format(tenant_id)
+    if name:
+
+        ret += ' ({0})'.format(name)
+    return ret
+
+
+def project_delete(project_id=None, name=None, profile=None, **connection_args):
+    '''
+    Delete a project (keystone project-delete).
+    Overrides keystone tenant-delete form api V2. For keystone api V3 only.
+
+    .. versionadded:: 2016.11.0
+
+    project_id
+        The project id.
+
+    name
+        The project name.
+
+    profile
+        Configuration profile - if configuration for multiple openstack accounts required.
+
+    CLI Examples:
+
+    .. code-block:: bash
+
+        salt '*' keystone.project_delete c965f79c4f864eaaa9c3b41904e67082
+        salt '*' keystone.project_delete project_id=c965f79c4f864eaaa9c3b41904e67082
+        salt '*' keystone.project_delete name=demo
+    '''
+    kstone = auth(profile, **connection_args)
+
+    if _client_version(kstone) > 2:
+        return tenant_delete(tenant_id=project_id, name=name, profile=None, **connection_args)
+    else:
+        return False
+
+
+def tenant_get(tenant_id=None, name=None, profile=None,
+               **connection_args):
+    '''
+    Return a specific tenants (keystone tenant-get)
+
+    CLI Examples:
+
+    .. code-block:: bash
+
+        salt '*' keystone.tenant_get c965f79c4f864eaaa9c3b41904e67082
+        salt '*' keystone.tenant_get tenant_id=c965f79c4f864eaaa9c3b41904e67082
+        salt '*' keystone.tenant_get name=nova
+    '''
+    kstone = auth(profile, **connection_args)
+    ret = {}
+
+    if name:
+        for tenant in _project_mgr(kstone).list():
+            if tenant.name == name:
+                tenant_id = tenant.id
+                break
+    if not tenant_id:
+        return {'Error': 'Unable to resolve tenant id'}
+    tenant = _project_mgr(kstone).get(tenant_id)
+    ret[tenant.name] = dict((value, getattr(tenant, value)) for value in dir(tenant)
+                            if not value.startswith('_') and
+                            isinstance(getattr(tenant, value), (six.string_types, dict, bool)))
+    return ret
+
+
+def project_get(project_id=None, name=None, profile=None, **connection_args):
+    '''
+    Return a specific projects (keystone project-get)
+    Overrides keystone tenant-get form api V2.
+    For keystone api V3 only.
+
+    .. versionadded:: 2016.11.0
+
+    project_id
+        The project id.
+
+    name
+        The project name.
+
+    profile
+        Configuration profile - if configuration for multiple openstack accounts required.
+
+    CLI Examples:
+
+    .. code-block:: bash
+
+        salt '*' keystone.project_get c965f79c4f864eaaa9c3b41904e67082
+        salt '*' keystone.project_get project_id=c965f79c4f864eaaa9c3b41904e67082
+        salt '*' keystone.project_get name=nova
+    '''
+    kstone = auth(profile, **connection_args)
+
+    if _client_version(kstone) > 2:
+        return tenant_get(tenant_id=project_id, name=name, profile=None, **connection_args)
+    else:
+        return False
+
+
+def tenant_list(profile=None, **connection_args):
+    '''
+    Return a list of available tenants (keystone tenants-list)
+
+    CLI Example:
+
+    .. code-block:: bash
+
+        salt '*' keystone.tenant_list
+    '''
+    kstone = auth(profile, **connection_args)
+    ret = {}
+
+    for tenant in _project_mgr(kstone).list():
+        ret[tenant.name] = dict((value, getattr(tenant, value)) for value in dir(tenant)
+                                if not value.startswith('_') and
+                                isinstance(getattr(tenant, value), (six.string_types, dict, bool)))
+    return ret
+
+
+def project_list(profile=None, **connection_args):
+    '''
+    Return a list of available projects (keystone projects-list).
+    Overrides keystone tenants-list form api V2.
+    For keystone api V3 only.
+
+    .. versionadded:: 2016.11.0
+
+    profile
+        Configuration profile - if configuration for multiple openstack accounts required.
+
+    CLI Example:
+
+    .. code-block:: bash
+
+        salt '*' keystone.project_list
+    '''
+    kstone = auth(profile, **connection_args)
+
+    if _client_version(kstone) > 2:
+        return tenant_list(profile, **connection_args)
+    else:
+        return False
+
+
+def tenant_update(tenant_id=None, name=None, description=None,
+                  enabled=None, profile=None, **connection_args):
+    '''
+    Update a tenant's information (keystone tenant-update)
+    The following fields may be updated: name, description, enabled.
+    Can only update name if targeting by ID
+
+    CLI Examples:
+
+    .. code-block:: bash
+
+        salt '*' keystone.tenant_update name=admin enabled=True
+        salt '*' keystone.tenant_update c965f79c4f864eaaa9c3b41904e67082 name=admin email=admin@domain.com
+    '''
+    kstone = auth(profile, **connection_args)
+
+    if not tenant_id:
+        for tenant in _project_mgr(kstone).list():
+            if tenant.name == name:
+                tenant_id = tenant.id
+                break
+    if not tenant_id:
+        return {'Error': 'Unable to resolve tenant id'}
+
+    tenant = _project_mgr(kstone).get(tenant_id)
+    if not name:
+        name = tenant.name
+    if not description:
+        description = tenant.description
+    if enabled is None:
+        enabled = tenant.enabled
+    updated = _project_mgr(kstone).update(tenant_id, name=name, description=description, enabled=enabled)
+
+    return dict((value, getattr(updated, value)) for value in dir(updated)
+                if not value.startswith('_') and
+                isinstance(getattr(updated, value), (six.string_types, dict, bool)))
+
+
+def project_update(project_id=None, name=None, description=None,
+                   enabled=None, profile=None, **connection_args):
+    '''
+    Update a tenant's information (keystone project-update)
+    The following fields may be updated: name, description, enabled.
+    Can only update name if targeting by ID
+
+    Overrides keystone tenant_update form api V2.
+    For keystone api V3 only.
+
+    .. versionadded:: 2016.11.0
+
+    project_id
+        The project id.
+
+    name
+        The project name, which must be unique within the owning domain.
+
+    description
+        The project description.
+
+    enabled
+        Enables or disables the project.
+
+    profile
+        Configuration profile - if configuration for multiple openstack accounts required.
+
+    CLI Examples:
+
+    .. code-block:: bash
+
+        salt '*' keystone.project_update name=admin enabled=True
+        salt '*' keystone.project_update c965f79c4f864eaaa9c3b41904e67082 name=admin email=admin@domain.com
+    '''
+    kstone = auth(profile, **connection_args)
+
+    if _client_version(kstone) > 2:
+        return tenant_update(tenant_id=project_id, name=name, description=description,
+                             enabled=enabled, profile=profile, **connection_args)
+    else:
+        return False
+
+
+def token_get(profile=None, **connection_args):
+    '''
+    Return the configured tokens (keystone token-get)
+
+    CLI Example:
+
+    .. code-block:: bash
+
+        salt '*' keystone.token_get c965f79c4f864eaaa9c3b41904e67082
+    '''
+    kstone = auth(profile, **connection_args)
+    token = kstone.service_catalog.get_token()
+    return {'id': token['id'],
+            'expires': token['expires'],
+            'user_id': token['user_id'],
+            'tenant_id': token['tenant_id']}
+
+
+def user_list(profile=None, **connection_args):
+    '''
+    Return a list of available users (keystone user-list)
+
+    CLI Example:
+
+    .. code-block:: bash
+
+        salt '*' keystone.user_list
+    '''
+    kstone = auth(profile, **connection_args)
+    ret = {}
+    for user in kstone.users.list():
+        ret[user.name] = dict((value, getattr(user, value, None)) for value in dir(user)
+                              if not value.startswith('_') and
+                              isinstance(getattr(user, value, None), (six.string_types, dict, bool)))
+        tenant_id = getattr(user, 'tenantId', None)
+        if tenant_id:
+            ret[user.name]['tenant_id'] = tenant_id
+    return ret
+
+
+def user_get(user_id=None, name=None, profile=None, **connection_args):
+    '''
+    Return a specific users (keystone user-get)
+
+    CLI Examples:
+
+    .. code-block:: bash
+
+        salt '*' keystone.user_get c965f79c4f864eaaa9c3b41904e67082
+        salt '*' keystone.user_get user_id=c965f79c4f864eaaa9c3b41904e67082
+        salt '*' keystone.user_get name=nova
+    '''
+    kstone = auth(profile, **connection_args)
+    ret = {}
+    if name:
+        for user in kstone.users.list():
+            if user.name == name:
+                user_id = user.id
+                break
+    if not user_id:
+        return {'Error': 'Unable to resolve user id'}
+    try:
+        user = kstone.users.get(user_id)
+    except keystoneclient.exceptions.NotFound:
+        msg = 'Could not find user \'{0}\''.format(user_id)
+        log.error(msg)
+        return {'Error': msg}
+
+    ret[user.name] = dict((value, getattr(user, value, None)) for value in dir(user)
+                          if not value.startswith('_') and
+                          isinstance(getattr(user, value, None), (six.string_types, dict, bool)))
+
+    tenant_id = getattr(user, 'tenantId', None)
+    if tenant_id:
+        ret[user.name]['tenant_id'] = tenant_id
+    return ret
+
+
+def user_create(name, password, email, tenant_id=None,
+                enabled=True, profile=None, project_id=None, description=None, **connection_args):
+    '''
+    Create a user (keystone user-create)
+
+    CLI Examples:
+
+    .. code-block:: bash
+
+        salt '*' keystone.user_create name=jack password=zero email=jack@halloweentown.org \
+        tenant_id=a28a7b5a999a455f84b1f5210264375e enabled=True
+    '''
+    kstone = auth(profile, **connection_args)
+
+    if _client_version(kstone) > 2:
+        if tenant_id and not project_id:
+            project_id = tenant_id
+        item = kstone.users.create(name=name,
+                                   password=password,
+                                   email=email,
+                                   project_id=project_id,
+                                   enabled=enabled,
+                                   description=description)
+    else:
+        item = kstone.users.create(name=name,
+                                   password=password,
+                                   email=email,
+                                   tenant_id=tenant_id,
+                                   enabled=enabled)
+    return user_get(item.id, profile=profile, **connection_args)
+
+
+def user_delete(user_id=None, name=None, profile=None, **connection_args):
+    '''
+    Delete a user (keystone user-delete)
+
+    CLI Examples:
+
+    .. code-block:: bash
+
+        salt '*' keystone.user_delete c965f79c4f864eaaa9c3b41904e67082
+        salt '*' keystone.user_delete user_id=c965f79c4f864eaaa9c3b41904e67082
+        salt '*' keystone.user_delete name=nova
+    '''
+    kstone = auth(profile, **connection_args)
+    if name:
+        for user in kstone.users.list():
+            if user.name == name:
+                user_id = user.id
+                break
+    if not user_id:
+        return {'Error': 'Unable to resolve user id'}
+    kstone.users.delete(user_id)
+    ret = 'User ID {0} deleted'.format(user_id)
+    if name:
+
+        ret += ' ({0})'.format(name)
+    return ret
+
+
+def user_update(user_id=None, name=None, email=None, enabled=None,
+                tenant=None, profile=None, project=None, description=None, **connection_args):
+    '''
+    Update a user's information (keystone user-update)
+    The following fields may be updated: name, email, enabled, tenant.
+    Because the name is one of the fields, a valid user id is required.
+
+    CLI Examples:
+
+    .. code-block:: bash
+
+        salt '*' keystone.user_update user_id=c965f79c4f864eaaa9c3b41904e67082 name=newname
+        salt '*' keystone.user_update c965f79c4f864eaaa9c3b41904e67082 name=newname email=newemail@domain.com
+    '''
+    kstone = auth(profile, **connection_args)
+    if not user_id:
+        for user in kstone.users.list():
+            if user.name == name:
+                user_id = user.id
+                break
+        if not user_id:
+            return {'Error': 'Unable to resolve user id'}
+    user = kstone.users.get(user_id)
+    # Keep previous settings if not updating them
+    if not name:
+        name = user.name
+    if not email:
+        email = user.email
+    if enabled is None:
+        enabled = user.enabled
+
+    if _client_version(kstone) > 2:
+        if description is None:
+            description = getattr(user, 'description', None)
+        else:
+            description = str(description)
+
+        project_id = None
+        if project:
+            for proj in kstone.projects.list():
+                if proj.name == project:
+                    project_id = proj.id
+                    break
+        if not project_id:
+            project_id = getattr(user, 'project_id', None)
+
+        kstone.users.update(user=user_id, name=name, email=email, enabled=enabled, description=description,
+                            project_id=project_id)
+    else:
+        kstone.users.update(user=user_id, name=name, email=email, enabled=enabled)
+
+        tenant_id = None
+        if tenant:
+            for tnt in kstone.tenants.list():
+                if tnt.name == tenant:
+                    tenant_id = tnt.id
+                    break
+            if tenant_id:
+                kstone.users.update_tenant(user_id, tenant_id)
+
+    ret = 'Info updated for user ID {0}'.format(user_id)
+    return ret
+
+
+def user_verify_password(user_id=None, name=None, password=None,
+                         profile=None, **connection_args):
+    '''
+    Verify a user's password
+
+    CLI Examples:
+
+    .. code-block:: bash
+
+        salt '*' keystone.user_verify_password name=test password=foobar
+        salt '*' keystone.user_verify_password user_id=c965f79c4f864eaaa9c3b41904e67082 password=foobar
+    '''
+    kstone = auth(profile, **connection_args)
+    if 'connection_endpoint' in connection_args:
+        auth_url = connection_args.get('connection_endpoint')
+    else:
+        if _client_version(kstone) > 2:
+            auth_url = __salt__['config.option']('keystone.endpoint',
+                                                 'http://127.0.0.1:35357/v3')
+        else:
+            auth_url = __salt__['config.option']('keystone.endpoint',
+                                                 'http://127.0.0.1:35357/v2.0')
+
+    if user_id:
+        for user in kstone.users.list():
+            if user.id == user_id:
+                name = user.name
+                break
+    if not name:
+        return {'Error': 'Unable to resolve user name'}
+    kwargs = {'username': name,
+              'password': password,
+              'auth_url': auth_url}
+    try:
+        if _client_version(kstone) > 2:
+            client3.Client(**kwargs)
+        else:
+            client.Client(**kwargs)
+    except (keystoneclient.exceptions.Unauthorized,
+            keystoneclient.exceptions.AuthorizationFailure):
+        return False
+    return True
+
+
+def user_password_update(user_id=None, name=None, password=None,
+                         profile=None, **connection_args):
+    '''
+    Update a user's password (keystone user-password-update)
+
+    CLI Examples:
+
+    .. code-block:: bash
+
+        salt '*' keystone.user_password_update c965f79c4f864eaaa9c3b41904e67082 password=12345
+        salt '*' keystone.user_password_update user_id=c965f79c4f864eaaa9c3b41904e67082 password=12345
+        salt '*' keystone.user_password_update name=nova password=12345
+    '''
+    kstone = auth(profile, **connection_args)
+    if name:
+        for user in kstone.users.list():
+            if user.name == name:
+                user_id = user.id
+                break
+    if not user_id:
+        return {'Error': 'Unable to resolve user id'}
+
+    if _client_version(kstone) > 2:
+        kstone.users.update(user=user_id, password=password)
+    else:
+        kstone.users.update_password(user=user_id, password=password)
+    ret = 'Password updated for user ID {0}'.format(user_id)
+    if name:
+        ret += ' ({0})'.format(name)
+    return ret
+
+
+def user_role_add(user_id=None, user=None, tenant_id=None,
+                  tenant=None, role_id=None, role=None, profile=None,
+                  project_id=None, project_name=None, **connection_args):
+    '''
+    Add role for user in tenant (keystone user-role-add)
+
+    CLI Examples:
+
+    .. code-block:: bash
+
+        salt '*' keystone.user_role_add \
+user_id=298ce377245c4ec9b70e1c639c89e654 \
+tenant_id=7167a092ece84bae8cead4bf9d15bb3b \
+role_id=ce377245c4ec9b70e1c639c89e8cead4
+        salt '*' keystone.user_role_add user=admin tenant=admin role=admin
+    '''
+    kstone = auth(profile, **connection_args)
+
+    if project_id and not tenant_id:
+        tenant_id = project_id
+    elif project_name and not tenant:
+        tenant = project_name
+
+    if user:
+        user_id = user_get(name=user, profile=profile,
+                           **connection_args)[user].get('id')
+    else:
+        user = next(six.iterkeys(user_get(user_id, profile=profile,
+                                          **connection_args)))['name']
+    if not user_id:
+        return {'Error': 'Unable to resolve user id'}
+
+    if tenant:
+        tenant_id = tenant_get(name=tenant, profile=profile,
+                               **connection_args)[tenant].get('id')
+    else:
+        tenant = next(six.iterkeys(tenant_get(tenant_id, profile=profile,
+                                              **connection_args)))['name']
+    if not tenant_id:
+        return {'Error': 'Unable to resolve tenant/project id'}
+
+    if role:
+        role_id = role_get(name=role, profile=profile,
+                           **connection_args)[role]['id']
+    else:
+        role = next(six.iterkeys(role_get(role_id, profile=profile,
+                                          **connection_args)))['name']
+    if not role_id:
+        return {'Error': 'Unable to resolve role id'}
+
+    if _client_version(kstone) > 2:
+        kstone.roles.grant(role_id, user=user_id, project=tenant_id)
+    else:
+        kstone.roles.add_user_role(user_id, role_id, tenant_id)
+    ret_msg = '"{0}" role added for user "{1}" for "{2}" tenant/project'
+    return ret_msg.format(role, user, tenant)
+
+
+def user_role_remove(user_id=None, user=None, tenant_id=None,
+                     tenant=None, role_id=None, role=None,
+                     profile=None, project_id=None, project_name=None, **connection_args):
+    '''
+    Remove role for user in tenant (keystone user-role-remove)
+
+    CLI Examples:
+
+    .. code-block:: bash
+
+        salt '*' keystone.user_role_remove \
+user_id=298ce377245c4ec9b70e1c639c89e654 \
+tenant_id=7167a092ece84bae8cead4bf9d15bb3b \
+role_id=ce377245c4ec9b70e1c639c89e8cead4
+        salt '*' keystone.user_role_remove user=admin tenant=admin role=admin
+    '''
+    kstone = auth(profile, **connection_args)
+
+    if project_id and not tenant_id:
+        tenant_id = project_id
+    elif project_name and not tenant:
+        tenant = project_name
+
+    if user:
+        user_id = user_get(name=user, profile=profile,
+                           **connection_args)[user].get('id')
+    else:
+        user = next(six.iterkeys(user_get(user_id, profile=profile,
+                                          **connection_args)))['name']
+    if not user_id:
+        return {'Error': 'Unable to resolve user id'}
+
+    if tenant:
+        tenant_id = tenant_get(name=tenant, profile=profile,
+                               **connection_args)[tenant].get('id')
+    else:
+        tenant = next(six.iterkeys(tenant_get(tenant_id, profile=profile,
+                                              **connection_args)))['name']
+    if not tenant_id:
+        return {'Error': 'Unable to resolve tenant/project id'}
+
+    if role:
+        role_id = role_get(name=role, profile=profile,
+                           **connection_args)[role]['id']
+    else:
+        role = next(six.iterkeys(role_get(role_id)))['name']
+    if not role_id:
+        return {'Error': 'Unable to resolve role id'}
+
+    if _client_version(kstone) > 2:
+        kstone.roles.revoke(role_id, user=user_id, project=tenant_id)
+    else:
+        kstone.roles.remove_user_role(user_id, role_id, tenant_id)
+    ret_msg = '"{0}" role removed for user "{1}" under "{2}" tenant'
+    return ret_msg.format(role, user, tenant)
+
+
+def user_role_list(user_id=None, tenant_id=None, user_name=None,
+                   tenant_name=None, profile=None, project_id=None, project_name=None, **connection_args):
+    '''
+    Return a list of available user_roles (keystone user-roles-list)
+
+    CLI Examples:
+
+    .. code-block:: bash
+
+        salt '*' keystone.user_role_list \
+user_id=298ce377245c4ec9b70e1c639c89e654 \
+tenant_id=7167a092ece84bae8cead4bf9d15bb3b
+        salt '*' keystone.user_role_list user_name=admin tenant_name=admin
+    '''
+    kstone = auth(profile, **connection_args)
+    ret = {}
+
+    if project_id and not tenant_id:
+        tenant_id = project_id
+    elif project_name and not tenant_name:
+        tenant_name = project_name
+
+    if user_name:
+        for user in kstone.users.list():
+            if user.name == user_name:
+                user_id = user.id
+                break
+    if tenant_name:
+        for tenant in _project_mgr(kstone).list():
+            if tenant.name == tenant_name:
+                tenant_id = tenant.id
+                break
+    if not user_id or not tenant_id:
+        return {'Error': 'Unable to resolve user or tenant/project id'}
+
+    if _client_version(kstone) > 2:
+        for role in kstone.roles.list(user=user_id, project=tenant_id):
+            ret[role.name] = dict((value, getattr(role, value)) for value in dir(role)
+                                  if not value.startswith('_') and
+                                  isinstance(getattr(role, value), (six.string_types, dict, bool)))
+    else:
+        for role in kstone.roles.roles_for_user(user=user_id, tenant=tenant_id):
+            ret[role.name] = {'id': role.id,
+                              'name': role.name,
+                              'user_id': user_id,
+                              'tenant_id': tenant_id}
+    return ret
+
+
+def _item_list(profile=None, **connection_args):
+    '''
+    Template for writing list functions
+    Return a list of available items (keystone items-list)
+
+    CLI Example:
+
+    .. code-block:: bash
+
+        salt '*' keystone.item_list
+    '''
+    kstone = auth(profile, **connection_args)
+    ret = []
+    for item in kstone.items.list():
+        ret.append(item.__dict__)
+        # ret[item.name] = {
+        #         'id': item.id,
+        #         'name': item.name,
+        #         }
+    return ret
+
+    # The following is a list of functions that need to be incorporated in the
+    # keystone module. This list should be updated as functions are added.
+    #
+    # endpoint-create     Create a new endpoint associated with a service
+    # endpoint-delete     Delete a service endpoint
+    # discover            Discover Keystone servers and show authentication
+    #                     protocols and
+    # bootstrap           Grants a new role to a new user on a new tenant, after
+    #                     creating each.
diff --git a/_states/keystoneng.py b/_states/keystoneng.py
new file mode 100644
index 0000000..060ccfb
--- /dev/null
+++ b/_states/keystoneng.py
@@ -0,0 +1,810 @@
+# -*- coding: utf-8 -*-
+'''
+Management of Keystone users
+============================
+
+:depends:   - keystoneclient Python module
+:configuration: See :py:mod:`salt.modules.keystone` for setup instructions.
+
+.. code-block:: yaml
+
+    Keystone tenants:
+      keystoneng.tenant_present:
+        - names:
+          - admin
+          - demo
+          - service
+
+    Keystone roles:
+      keystoneng.role_present:
+        - names:
+          - admin
+          - Member
+
+    admin:
+      keystoneng.user_present:
+        - password: R00T_4CC3SS
+        - email: admin@domain.com
+        - roles:
+            admin:   # tenants
+              - admin  # roles
+            service:
+              - admin
+              - Member
+        - require:
+          - keystone: Keystone tenants
+          - keystone: Keystone roles
+
+    nova:
+      keystoneng.user_present:
+        - password: '$up3rn0v4'
+        - email: nova@domain.com
+        - tenant: service
+        - roles:
+            service:
+              - admin
+        - require:
+          - keystone: Keystone tenants
+          - keystone: Keystone roles
+
+    demo:
+      keystoneng.user_present:
+        - password: 'd3m0n$trati0n'
+        - email: demo@domain.com
+        - tenant: demo
+        - roles:
+            demo:
+              - Member
+        - require:
+          - keystone: Keystone tenants
+          - keystone: Keystone roles
+
+    nova service:
+      keystoneng.service_present:
+        - name: nova
+        - service_type: compute
+        - description: OpenStack Compute Service
+
+'''
+
+
+def __virtual__():
+    '''
+    Only load if the keystone module is in __salt__
+    '''
+    return 'keystoneng' if 'keystoneng.auth' in __salt__ else False
+
+
+_OS_IDENTITY_API_VERSION = 2
+_TENANT_ID = 'tenant_id'
+
+
+def _api_version(profile=None, **connection_args):
+    '''
+    Sets global variables _OS_IDENTITY_API_VERSION and _TENANT_ID
+    depending on API version.
+    '''
+    global _TENANT_ID
+    global _OS_IDENTITY_API_VERSION
+    try:
+        if float(__salt__['keystoneng.api_version'](profile=profile, **connection_args)) >= 3:
+            _TENANT_ID = 'project_id'
+            _OS_IDENTITY_API_VERSION = 3
+    except KeyError:
+        pass
+
+
+def user_present(name,
+                 password,
+                 email,
+                 tenant=None,
+                 enabled=True,
+                 roles=None,
+                 profile=None,
+                 password_reset=True,
+                 project=None,
+                 **connection_args):
+    '''
+    Ensure that the keystone user is present with the specified properties.
+
+    name
+        The name of the user to manage
+
+    password
+        The password to use for this user.
+
+        .. note::
+
+            If the user already exists and a different password was set for
+            the user than the one specified here, the password for the user
+            will be updated. Please set the ``password_reset`` option to
+            ``False`` if this is not the desired behavior.
+
+    password_reset
+        Whether or not to reset password after initial set. Defaults to
+        ``True``.
+
+    email
+        The email address for this user
+
+    tenant
+        The tenant (name) for this user
+
+    project
+        The project (name) for this user (overrides tenant in api v3)
+
+    enabled
+        Availability state for this user
+
+    roles
+        The roles the user should have under given tenants.
+        Passed as a dictionary mapping tenant names to a list
+        of roles in this tenant, i.e.::
+
+            roles:
+                admin:   # tenant
+                  - admin  # role
+                service:
+                  - admin
+                  - Member
+    '''
+    ret = {'name': name,
+           'changes': {},
+           'result': True,
+           'comment': 'User "{0}" will be updated'.format(name)}
+
+    _api_version(profile=profile, **connection_args)
+
+    if project and not tenant:
+        tenant = project
+
+    # Validate tenant if set
+    if tenant is not None:
+        tenantdata = __salt__['keystoneng.tenant_get'](name=tenant,
+                                                     profile=profile,
+                                                     **connection_args)
+        if 'Error' in tenantdata:
+            ret['result'] = False
+            ret['comment'] = 'Tenant / project "{0}" does not exist'.format(tenant)
+            return ret
+        tenant_id = tenantdata[tenant]['id']
+    else:
+        tenant_id = None
+
+    # Check if user is already present
+    user = __salt__['keystoneng.user_get'](name=name, profile=profile,
+                                         **connection_args)
+    if 'Error' not in user:
+
+        change_email = False
+        change_enabled = False
+        change_tenant = False
+        change_password = False
+
+        if user[name].get('email', None) != email:
+            change_email = True
+
+        if user[name].get('enabled', None) != enabled:
+            change_enabled = True
+
+        if tenant and (_TENANT_ID not in user[name] or
+                       user[name].get(_TENANT_ID, None) != tenant_id):
+            change_tenant = True
+
+        if (password_reset is True and
+            not __salt__['keystoneng.user_verify_password'](name=name,
+                                                          password=password,
+                                                          profile=profile,
+                                                          **connection_args)):
+            change_password = True
+
+        if __opts__.get('test') and (change_email or change_enabled or change_tenant or change_password):
+            ret['result'] = None
+            ret['comment'] = 'User "{0}" will be updated'.format(name)
+            if change_email is True:
+                ret['changes']['Email'] = 'Will be updated'
+            if change_enabled is True:
+                ret['changes']['Enabled'] = 'Will be True'
+            if change_tenant is True:
+                ret['changes']['Tenant'] = 'Will be added to "{0}" tenant'.format(tenant)
+            if change_password is True:
+                ret['changes']['Password'] = 'Will be updated'
+            return ret
+
+        ret['comment'] = 'User "{0}" is already present'.format(name)
+
+        if change_email:
+            __salt__['keystoneng.user_update'](name=name, email=email, profile=profile, **connection_args)
+            ret['comment'] = 'User "{0}" has been updated'.format(name)
+            ret['changes']['Email'] = 'Updated'
+
+        if change_enabled:
+            __salt__['keystoneng.user_update'](name=name, enabled=enabled, profile=profile, **connection_args)
+            ret['comment'] = 'User "{0}" has been updated'.format(name)
+            ret['changes']['Enabled'] = 'Now {0}'.format(enabled)
+
+        if change_tenant:
+            __salt__['keystoneng.user_update'](name=name, tenant=tenant, profile=profile, **connection_args)
+            ret['comment'] = 'User "{0}" has been updated'.format(name)
+            ret['changes']['Tenant'] = 'Added to "{0}" tenant'.format(tenant)
+
+        if change_password:
+            __salt__['keystoneng.user_password_update'](name=name, password=password, profile=profile,
+                                                      **connection_args)
+            ret['comment'] = 'User "{0}" has been updated'.format(name)
+            ret['changes']['Password'] = 'Updated'
+
+        if roles:
+            for tenant in roles:
+                args = dict({'user_name': name, 'tenant_name':
+                             tenant, 'profile': profile}, **connection_args)
+                tenant_roles = __salt__['keystoneng.user_role_list'](**args)
+                for role in roles[tenant]:
+                    if role not in tenant_roles:
+                        if __opts__.get('test'):
+                            ret['result'] = None
+                            ret['comment'] = 'User roles "{0}" will been updated'.format(name)
+                            return ret
+                        addargs = dict({'user': name, 'role': role,
+                                        'tenant': tenant,
+                                        'profile': profile},
+                                       **connection_args)
+                        newrole = __salt__['keystoneng.user_role_add'](**addargs)
+                        if 'roles' in ret['changes']:
+                            ret['changes']['roles'].append(newrole)
+                        else:
+                            ret['changes']['roles'] = [newrole]
+                roles_to_remove = list(set(tenant_roles) - set(roles[tenant]))
+                for role in roles_to_remove:
+                    if __opts__.get('test'):
+                        ret['result'] = None
+                        ret['comment'] = 'User roles "{0}" will been updated'.format(name)
+                        return ret
+                    addargs = dict({'user': name, 'role': role,
+                                    'tenant': tenant,
+                                    'profile': profile},
+                                   **connection_args)
+                    oldrole = __salt__['keystoneng.user_role_remove'](**addargs)
+                    if 'roles' in ret['changes']:
+                        ret['changes']['roles'].append(oldrole)
+                    else:
+                        ret['changes']['roles'] = [oldrole]
+    else:
+        # Create that user!
+        if __opts__.get('test'):
+            ret['result'] = None
+            ret['comment'] = 'Keystone user "{0}" will be added'.format(name)
+            ret['changes']['User'] = 'Will be created'
+            return ret
+        __salt__['keystoneng.user_create'](name=name,
+                                         password=password,
+                                         email=email,
+                                         tenant_id=tenant_id,
+                                         enabled=enabled,
+                                         profile=profile,
+                                         **connection_args)
+        if roles:
+            for tenant in roles:
+                for role in roles[tenant]:
+                    __salt__['keystoneng.user_role_add'](user=name,
+                                                       role=role,
+                                                       tenant=tenant,
+                                                       profile=profile,
+                                                       **connection_args)
+        ret['comment'] = 'Keystone user {0} has been added'.format(name)
+        ret['changes']['User'] = 'Created'
+
+    return ret
+
+
+def user_absent(name, profile=None, **connection_args):
+    '''
+    Ensure that the keystone user is absent.
+
+    name
+        The name of the user that should not exist
+    '''
+    ret = {'name': name,
+           'changes': {},
+           'result': True,
+           'comment': 'User "{0}" is already absent'.format(name)}
+
+    # Check if user is present
+    user = __salt__['keystoneng.user_get'](name=name, profile=profile,
+                                         **connection_args)
+    if 'Error' not in user:
+        if __opts__.get('test'):
+            ret['result'] = None
+            ret['comment'] = 'User "{0}" will be deleted'.format(name)
+            return ret
+        # Delete that user!
+        __salt__['keystoneng.user_delete'](name=name, profile=profile,
+                                         **connection_args)
+        ret['comment'] = 'User "{0}" has been deleted'.format(name)
+        ret['changes']['User'] = 'Deleted'
+
+    return ret
+
+
+def tenant_present(name, description=None, enabled=True, profile=None,
+                   **connection_args):
+    '''
+    Ensures that the keystone tenant exists
+
+    name
+        The name of the tenant to manage
+
+    description
+        The description to use for this tenant
+
+    enabled
+        Availability state for this tenant
+    '''
+    ret = {'name': name,
+           'changes': {},
+           'result': True,
+           'comment': 'Tenant / project "{0}" already exists'.format(name)}
+
+    _api_version(profile=profile, **connection_args)
+
+    # Check if tenant is already present
+    tenant = __salt__['keystoneng.tenant_get'](name=name,
+                                             profile=profile,
+                                             **connection_args)
+
+    if 'Error' not in tenant:
+        if tenant[name].get('description', None) != description:
+            if __opts__.get('test'):
+                ret['result'] = None
+                ret['comment'] = 'Tenant / project "{0}" will be updated'.format(name)
+                ret['changes']['Description'] = 'Will be updated'
+                return ret
+            __salt__['keystoneng.tenant_update'](name=name,
+                                               description=description,
+                                               enabled=enabled,
+                                               profile=profile,
+                                               **connection_args)
+            ret['comment'] = 'Tenant / project "{0}" has been updated'.format(name)
+            ret['changes']['Description'] = 'Updated'
+        if tenant[name].get('enabled', None) != enabled:
+            if __opts__.get('test'):
+                ret['result'] = None
+                ret['comment'] = 'Tenant / project "{0}" will be updated'.format(name)
+                ret['changes']['Enabled'] = 'Will be {0}'.format(enabled)
+                return ret
+            __salt__['keystoneng.tenant_update'](name=name,
+                                               description=description,
+                                               enabled=enabled,
+                                               profile=profile,
+                                               **connection_args)
+            ret['comment'] = 'Tenant / project "{0}" has been updated'.format(name)
+            ret['changes']['Enabled'] = 'Now {0}'.format(enabled)
+    else:
+        if __opts__.get('test'):
+            ret['result'] = None
+            ret['comment'] = 'Tenant / project "{0}" will be added'.format(name)
+            ret['changes']['Tenant'] = 'Will be created'
+            return ret
+        # Create tenant
+        if _OS_IDENTITY_API_VERSION > 2:
+            created = __salt__['keystoneng.project_create'](name=name, domain='default', description=description,
+                                                          enabled=enabled, profile=profile, **connection_args)
+        else:
+            created = __salt__['keystoneng.tenant_create'](name=name, description=description, enabled=enabled,
+                                                         profile=profile, **connection_args)
+        ret['changes']['Tenant'] = 'Created' if created is True else 'Failed'
+        ret['result'] = created
+        ret['comment'] = 'Tenant / project "{0}" has been added'.format(name)
+    return ret
+
+
+def tenant_absent(name, profile=None, **connection_args):
+    '''
+    Ensure that the keystone tenant is absent.
+
+    name
+        The name of the tenant that should not exist
+    '''
+    ret = {'name': name,
+           'changes': {},
+           'result': True,
+           'comment': 'Tenant / project "{0}" is already absent'.format(name)}
+
+    # Check if tenant is present
+    tenant = __salt__['keystoneng.tenant_get'](name=name,
+                                             profile=profile,
+                                             **connection_args)
+    if 'Error' not in tenant:
+        if __opts__.get('test'):
+            ret['result'] = None
+            ret['comment'] = 'Tenant / project "{0}" will be deleted'.format(name)
+            return ret
+        # Delete tenant
+        __salt__['keystoneng.tenant_delete'](name=name, profile=profile,
+                                           **connection_args)
+        ret['comment'] = 'Tenant / project "{0}" has been deleted'.format(name)
+        ret['changes']['Tenant/Project'] = 'Deleted'
+
+    return ret
+
+
+def project_present(name, description=None, enabled=True, profile=None,
+                    **connection_args):
+    '''
+    Ensures that the keystone project exists
+    Alias for tenant_present from V2 API to fulfill
+    V3 API naming convention.
+
+    .. versionadded:: 2016.11.0
+
+    name
+        The name of the project to manage
+
+    description
+        The description to use for this project
+
+    enabled
+        Availability state for this project
+
+    .. code-block:: yaml
+
+        nova:
+            keystoneng.project_present:
+                - enabled: True
+                - description: 'Nova Compute Service'
+
+    '''
+
+    return tenant_present(name, description=description, enabled=enabled, profile=profile,
+                          **connection_args)
+
+
+def project_absent(name, profile=None, **connection_args):
+    '''
+    Ensure that the keystone project is absent.
+    Alias for tenant_absent from V2 API to fulfill
+    V3 API naming convention.
+
+    .. versionadded:: 2016.11.0
+
+    name
+        The name of the project that should not exist
+
+    .. code-block:: yaml
+
+        delete_nova:
+            keystoneng.project_absent:
+                - name: nova
+    '''
+
+    return tenant_absent(name, profile=profile, **connection_args)
+
+
+def role_present(name, profile=None, **connection_args):
+    ''''
+    Ensures that the keystone role exists
+
+    name
+        The name of the role that should be present
+    '''
+    ret = {'name': name,
+           'changes': {},
+           'result': True,
+           'comment': 'Role "{0}" already exists'.format(name)}
+
+    # Check if role is already present
+    role = __salt__['keystoneng.role_get'](name=name, profile=profile,
+                                         **connection_args)
+
+    if 'Error' not in role:
+        return ret
+    else:
+        if __opts__.get('test'):
+            ret['result'] = None
+            ret['comment'] = 'Role "{0}" will be added'.format(name)
+            return ret
+        # Create role
+        __salt__['keystoneng.role_create'](name, profile=profile,
+                                         **connection_args)
+        ret['comment'] = 'Role "{0}" has been added'.format(name)
+        ret['changes']['Role'] = 'Created'
+    return ret
+
+
+def role_absent(name, profile=None, **connection_args):
+    '''
+    Ensure that the keystone role is absent.
+
+    name
+        The name of the role that should not exist
+    '''
+    ret = {'name': name,
+           'changes': {},
+           'result': True,
+           'comment': 'Role "{0}" is already absent'.format(name)}
+
+    # Check if role is present
+    role = __salt__['keystoneng.role_get'](name=name, profile=profile,
+                                         **connection_args)
+    if 'Error' not in role:
+        if __opts__.get('test'):
+            ret['result'] = None
+            ret['comment'] = 'Role "{0}" will be deleted'.format(name)
+            return ret
+        # Delete role
+        __salt__['keystoneng.role_delete'](name=name, profile=profile,
+                                         **connection_args)
+        ret['comment'] = 'Role "{0}" has been deleted'.format(name)
+        ret['changes']['Role'] = 'Deleted'
+
+    return ret
+
+
+def service_present(name, service_type, description=None,
+                    profile=None, **connection_args):
+    '''
+    Ensure service present in Keystone catalog
+
+    name
+        The name of the service
+
+    service_type
+        The type of Openstack Service
+
+    description (optional)
+        Description of the service
+    '''
+    ret = {'name': name,
+           'changes': {},
+           'result': True,
+           'comment': 'Service "{0}" already exists'.format(name)}
+
+    # Check if service is already present
+    role = __salt__['keystoneng.service_get'](name=name,
+                                            profile=profile,
+                                            **connection_args)
+
+    if 'Error' not in role:
+        return ret
+    else:
+        if __opts__.get('test'):
+            ret['result'] = None
+            ret['comment'] = 'Service "{0}" will be added'.format(name)
+            return ret
+        # Create service
+        __salt__['keystoneng.service_create'](name, service_type,
+                                            description,
+                                            profile=profile,
+                                            **connection_args)
+        ret['comment'] = 'Service "{0}" has been added'.format(name)
+        ret['changes']['Service'] = 'Created'
+
+    return ret
+
+
+def service_absent(name, profile=None, **connection_args):
+    '''
+    Ensure that the service doesn't exist in Keystone catalog
+
+    name
+        The name of the service that should not exist
+    '''
+    ret = {'name': name,
+           'changes': {},
+           'result': True,
+           'comment': 'Service "{0}" is already absent'.format(name)}
+
+    # Check if service is present
+    role = __salt__['keystoneng.service_get'](name=name,
+                                            profile=profile,
+                                            **connection_args)
+    if 'Error' not in role:
+        if __opts__.get('test'):
+            ret['result'] = None
+            ret['comment'] = 'Service "{0}" will be deleted'.format(name)
+            return ret
+        # Delete service
+        __salt__['keystoneng.service_delete'](name=name,
+                                            profile=profile,
+                                            **connection_args)
+        ret['comment'] = 'Service "{0}" has been deleted'.format(name)
+        ret['changes']['Service'] = 'Deleted'
+
+    return ret
+
+
+def endpoint_present(name,
+                     publicurl=None,
+                     internalurl=None,
+                     adminurl=None,
+                     region=None,
+                     profile=None,
+                     url=None,
+                     interface=None, **connection_args):
+    '''
+    Ensure the specified endpoints exists for service
+
+    name
+        The Service name
+
+    publicurl
+        The public url of service endpoint (for V2 API)
+
+    internalurl
+        The internal url of service endpoint (for V2 API)
+
+    adminurl
+        The admin url of the service endpoint (for V2 API)
+
+    region
+        The region of the endpoint
+
+    url
+        The endpoint URL (for V3 API)
+
+    interface
+        The interface type, which describes the visibility
+        of the endpoint. (for V3 API)
+
+    '''
+    ret = {'name': name,
+           'changes': {},
+           'result': True,
+           'comment': ''}
+    endpoint = __salt__['keystoneng.endpoint_get'](name, region,
+                                                 profile=profile,
+                                                 interface=interface,
+                                                 **connection_args)
+
+    def _changes(desc):
+        return ret.get('comment', '') + desc + '\n'
+
+    def _create_endpoint():
+        if _OS_IDENTITY_API_VERSION > 2:
+            ret['changes'] = __salt__['keystoneng.endpoint_create'](
+                name,
+                region=region,
+                url=url,
+                interface=interface,
+                profile=profile,
+                **connection_args)
+        else:
+            ret['changes'] = __salt__['keystoneng.endpoint_create'](
+                name,
+                region=region,
+                publicurl=publicurl,
+                adminurl=adminurl,
+                internalurl=internalurl,
+                profile=profile,
+                **connection_args)
+
+    if endpoint and 'Error' not in endpoint and endpoint.get('region') == region:
+
+        if _OS_IDENTITY_API_VERSION > 2:
+
+            change_url = False
+            change_interface = False
+
+            if endpoint.get('url', None) != url:
+                ret['comment'] = _changes('URL changes from "{0}" to "{1}"'.format(endpoint.get('url', None), url))
+                change_url = True
+
+            if endpoint.get('interface', None) != interface:
+                ret['comment'] = _changes('Interface changes from "{0}" to "{1}"'.format(endpoint.get('interface', None), interface))
+                change_interface = True
+
+            if __opts__.get('test') and (change_url or change_interface):
+                ret['result'] = None
+                ret['changes']['Endpoint'] = 'Will be updated'
+                ret['comment'] += 'Endpoint for service "{0}" will be updated'.format(name)
+                return ret
+
+            if change_url:
+                ret['changes']['url'] = url
+
+            if change_interface:
+                ret['changes']['interface'] = interface
+
+        else:
+            change_publicurl = False
+            change_adminurl = False
+            change_internalurl = False
+
+            if endpoint.get('publicurl', None) != publicurl:
+                change_publicurl = True
+
+                ret['comment'] = _changes('Public URL changes from "{0}" to "{1}"'.format(
+                    endpoint.get('publicurl', None), publicurl)
+                )
+
+            if endpoint.get('adminurl', None) != adminurl:
+                change_adminurl = True
+                ret['comment'] = _changes('Admin URL changes from "{0}" to "{1}"'.format(
+                    endpoint.get('adminurl', None), adminurl)
+                )
+
+            if endpoint.get('internalurl', None) != internalurl:
+                change_internalurl = True
+                ret['comment'] = _changes(
+                    'Internal URL changes from "{0}" to "{1}"'.format(
+                        endpoint.get('internalurl', None),
+                        internalurl
+                    )
+                )
+
+            if __opts__.get('test') and (change_publicurl or change_adminurl or change_internalurl):
+                ret['result'] = None
+                ret['comment'] += 'Endpoint for service "{0}" will be updated'.format(name)
+                ret['changes']['Endpoint'] = 'Will be updated'
+                return ret
+
+            if change_publicurl:
+                ret['changes']['publicurl'] = publicurl
+
+            if change_adminurl:
+                ret['changes']['adminurl'] = adminurl
+
+            if change_internalurl:
+                ret['changes']['internalurl'] = internalurl
+
+        if ret['comment']:  # changed
+            __salt__['keystoneng.endpoint_delete'](name, region, profile=profile, interface=interface, **connection_args)
+            _create_endpoint()
+            ret['comment'] += 'Endpoint for service "{0}" has been updated'.format(name)
+
+    else:
+        # Add new endpoint
+        if __opts__.get('test'):
+            ret['result'] = None
+            ret['changes']['Endpoint'] = 'Will be created'
+            ret['comment'] = 'Endpoint for service "{0}" will be added'.format(name)
+            return ret
+        _create_endpoint()
+        ret['comment'] = 'Endpoint for service "{0}" has been added'.format(name)
+
+    if ret['comment'] == '':  # => no changes
+        ret['comment'] = 'Endpoint for service "{0}" already exists'.format(name)
+    return ret
+
+
+def endpoint_absent(name, region=None, profile=None, interface=None, **connection_args):
+    '''
+    Ensure that the endpoint for a service doesn't exist in Keystone catalog
+
+    name
+        The name of the service whose endpoints should not exist
+
+    region (optional)
+        The region of the endpoint.  Defaults to ``RegionOne``.
+
+    interface
+        The interface type, which describes the visibility
+        of the endpoint. (for V3 API)
+    '''
+    ret = {'name': name,
+           'changes': {},
+           'result': True,
+           'comment': 'Endpoint for service "{0}"{1} is already absent'.format(name,
+                      ', interface "{0}",'.format(interface) if interface is not None else '')}
+
+    # Check if service is present
+    endpoint = __salt__['keystoneng.endpoint_get'](name, region,
+                                                 profile=profile,
+                                                 interface=interface,
+                                                 **connection_args)
+    if not endpoint:
+        return ret
+    else:
+        if __opts__.get('test'):
+            ret['result'] = None
+            ret['comment'] = 'Endpoint for service "{0}" will be deleted'.format(name)
+            return ret
+        # Delete service
+        __salt__['keystoneng.endpoint_delete'](name, region,
+                                             profile=profile,
+                                             interface=interface,
+                                             **connection_args)
+        ret['comment'] = 'Endpoint for service "{0}"{1} has been deleted'.format(name,
+                         ', interface "{0}",'.format(interface) if interface is not None else '')
+        ret['changes']['endpoint'] = 'Deleted'
+    return ret
diff --git a/keystone/client/project.sls b/keystone/client/project.sls
index 856c78f..5e07b45 100644
--- a/keystone/client/project.sls
+++ b/keystone/client/project.sls
@@ -4,7 +4,7 @@
 {%- if client.tenant is defined %}
 
 keystone_client_roles:
-  keystone.role_present:
+  keystoneng.role_present:
   - names: {{ client.roles }}
   - connection_user: {{ client.server.user }}
   - connection_password: {{ client.server.password }}
@@ -14,19 +14,19 @@
 {%- for tenant_name, tenant in client.get('tenant', {}).iteritems() %}
 
 keystone_tenant_{{ tenant_name }}:
-  keystone.tenant_present:
+  keystoneng.tenant_present:
   - name: {{ tenant_name }}
   - connection_user: {{ client.server.user }}
   - connection_password: {{ client.server.password }}
   - connection_tenant: {{ client.server.tenant }}
   - connection_auth_url: 'http://{{ client.server.host }}:{{ client.server.public_port }}/v2.0/'
   - require:
-    - keystone: keystone_client_roles
+    - keystoneng: keystone_client_roles
 
 {%- for user_name, user in tenant.get('user', {}).iteritems() %}
 
 keystone_{{ tenant_name }}_user_{{ user_name }}:
-  keystone.user_present:
+  keystoneng.user_present:
   - name: {{ user_name }}
   - password: {{ user.password }}
   - email: {{ user.get('email', 'root@localhost') }}
@@ -45,7 +45,7 @@
   - connection_tenant: {{ client.server.tenant }}
   - connection_auth_url: 'http://{{ client.server.host }}:{{ client.server.public_port }}/v2.0/'
   - require:
-    - keystone: keystone_tenant_{{ tenant_name }}
+    - keystoneng: keystone_tenant_{{ tenant_name }}
 
 {%- endfor %}
 
diff --git a/keystone/client/server.sls b/keystone/client/server.sls
index 416590b..fb7597b 100644
--- a/keystone/client/server.sls
+++ b/keystone/client/server.sls
@@ -28,7 +28,7 @@
 {%- if server.roles is defined %}
 
 keystone_{{ server_name }}_roles:
-  keystone.role_present:
+  keystoneng.role_present:
   - names: {{ server.roles }}
   {%- if server.admin.token is defined %}
   - connection_token: {{ connection_args.token }}
@@ -45,7 +45,7 @@
 {% for service_name, service in server.get('service', {}).iteritems() %}
 
 keystone_{{ server_name }}_service_{{ service_name }}:
-  keystone.service_present:
+  keystoneng.service_present:
   - name: {{ service_name }}
   - service_type: {{ service.type }}
   - description: {{ service.description }}
@@ -62,14 +62,14 @@
 {%- for endpoint in service.get('endpoints', ()) %}
 
 keystone_{{ server_name }}_service_{{ service_name }}_endpoint_{{ endpoint.region }}:
-  keystone.endpoint_present:
+  keystoneng.endpoint_present:
   - name: {{ service_name }}
   - publicurl: '{{ endpoint.get('public_protocol', 'http') }}://{{ endpoint.public_address }}{% if not (endpoint.get('public_protocol', 'http') == 'https' and endpoint.public_port|int == 443) %}:{{ endpoint.public_port }}{% endif %}{{ endpoint.public_path }}'
   - internalurl: '{{ endpoint.get('internal_protocol', 'http') }}://{{ endpoint.internal_address }}{% if not (endpoint.get('internal_protocol', 'http') == 'https' and endpoint.internal_port|int == 443) %}:{{ endpoint.internal_port }}{% endif %}{{ endpoint.internal_path }}'
   - adminurl: '{{ endpoint.get('admin_protocol', 'http') }}://{{ endpoint.admin_address }}{% if not (endpoint.get('admin_protocol', 'http') == 'https' and endpoint.admin_port|int == 443) %}:{{ endpoint.admin_port }}{% endif %}{{ endpoint.admin_path }}'
   - region: {{ endpoint.region }}
   - require:
-    - keystone: keystone_{{ server_name }}_service_{{ service_name }}
+    - keystoneng: keystone_{{ server_name }}_service_{{ service_name }}
   {%- if server.admin.token is defined %}
   - connection_token: {{ connection_args.token }}
   - connection_endpoint: {{ connection_args.endpoint }}
@@ -87,7 +87,7 @@
 {%- for tenant_name, tenant in server.get('project', {}).iteritems() %}
 
 keystone_{{ server_name }}_tenant_{{ tenant_name }}:
-  keystone.tenant_present:
+  keystoneng.tenant_present:
   - name: {{ tenant_name }}
   {%- if tenant.description is defined %}
   - description: {{ tenant.description }}
@@ -112,14 +112,14 @@
     - {{ quota_name }}: {{ quota_value }}
     {%- endfor %}
     - require:
-      - keystone: keystone_{{ server_name }}_tenant_{{ tenant_name }}
+      - keystoneng: keystone_{{ server_name }}_tenant_{{ tenant_name }}
 
 {%- endif %}
 
 {%- for user_name, user in tenant.get('user', {}).iteritems() %}
 
 keystone_{{ server_name }}_tenant_{{ tenant_name }}_user_{{ user_name }}:
-  keystone.user_present:
+  keystoneng.user_present:
   - name: {{ user_name }}
   - password: {{ user.password }}
   {%- if user.email is defined %}
@@ -136,8 +136,8 @@
         - Member
         {%- endif %}
   - require:
-    - keystone: keystone_{{ server_name }}_tenant_{{ tenant_name }}
-    - keystone: keystone_{{ server_name }}_roles
+    - keystoneng: keystone_{{ server_name }}_tenant_{{ tenant_name }}
+    - keystoneng: keystone_{{ server_name }}_roles
   {%- if server.admin.token is defined %}
   - connection_token: {{ connection_args.token }}
   - connection_endpoint: {{ connection_args.endpoint }}
diff --git a/keystone/meta/salt.yml b/keystone/meta/salt.yml
index 4a3f557..f287824 100644
--- a/keystone/meta/salt.yml
+++ b/keystone/meta/salt.yml
@@ -15,13 +15,13 @@
   keystone:
     {%- if pillar.keystone.get('server', {'enabled': False}).enabled %}
     keystone.token: '{{ server.service_token }}'
-    keystone.endpoint: '{{ server.bind.private_protocol }}://{{ server.bind.address }}:{{ server.bind.private_port }}/v2.0'
+    keystone.endpoint: '{{ server.bind.private_protocol }}://{{ server.bind.address }}:{{ server.bind.private_port }}/{{ server.bind.get('private_api_version', 'v2.0') }}'
     {%- else %}
       {%- if client.get('server', {}).get('user') %}
     keystone.user: '{{ client.server.user }}'
     keystone.password: '{{ client.server.password }}'
     keystone.tenant: '{{ client.server.tenant }}'
-    keystone.auth_url: '{{ client.server.get('public_protocol', 'http') }}://{{ client.server.host }}:{{ client.server.public_port }}/v2.0/'
+    keystone.auth_url: '{{ client.server.get('public_protocol', 'http') }}://{{ client.server.host }}:{{ client.server.public_port }}/{{ client.server.get('public_api_version', 'v2.0') }}'
       {%- endif %}
     {%- endif %}
 
@@ -30,9 +30,13 @@
       {%- set protocol = identity.admin.get('protocol', 'http') %}
 
       {%- if identity.admin.get('api_version', '2') == '3' %}
-        {%- set version = "v3" %}
-      {%- else %}
-        {%- set version = "v2.0" %}
+        {%- set version = "/v3" %}
+      {%- endif %}
+      {%- if identity.admin.get('api_version', '2') == '2' %}
+        {%- set version = "/v2.0" %}
+      {%- endif %}
+      {%- if identity.admin.get('api_version', '2') == '' %}
+        {%- set version = "" %}
       {%- endif %}
 
       {%- if identity.admin.user is defined %}
@@ -42,7 +46,7 @@
       keystone.user: '{{ identity.admin.user }}'
       keystone.password: '{{ identity.admin.password }}'
       keystone.tenant: '{{ identity.admin.project }}'
-      keystone.auth_url: '{{ protocol+'://'+identity.admin.host+':'+identity.admin.port|string+'/'+version }}'
+      keystone.auth_url: '{{ protocol+'://'+identity.admin.host+':'+identity.admin.port|string+version }}'
       keystone.region_name: '{{ identity.admin.region_name }}'
       keystone.use_keystoneauth: {{ identity.admin.get('use_keystoneauth', false) }}
 
diff --git a/keystone/server.sls b/keystone/server.sls
index 99337f9..4e4cba6 100644
--- a/keystone/server.sls
+++ b/keystone/server.sls
@@ -338,7 +338,7 @@
 {%- if not salt['pillar.get']('linux:system:repo:mirantis_openstack', False) %}
 
 keystone_service_tenant:
-  keystone.tenant_present:
+  keystoneng.tenant_present:
   - name: {{ server.service_tenant }}
   - connection_token: {{ server.service_token }}
   - connection_endpoint: 'http://{{ server.bind.address }}:{{ server.bind.private_port }}/v2.0'
@@ -346,25 +346,25 @@
     - cmd: keystone_syncdb
 
 keystone_admin_tenant:
-  keystone.tenant_present:
+  keystoneng.tenant_present:
   - name: {{ server.admin_tenant }}
   - connection_token: {{ server.service_token }}
   - connection_endpoint: 'http://{{ server.bind.address }}:{{ server.bind.private_port }}/v2.0'
   - require:
-    - keystone: keystone_service_tenant
+    - keystoneng: keystone_service_tenant
 
 keystone_roles:
-  keystone.role_present:
+  keystoneng.role_present:
   - names: {{ server.roles }}
   - connection_token: {{ server.service_token }}
   - connection_endpoint: 'http://{{ server.bind.address }}:{{ server.bind.private_port }}/v2.0'
   - require:
-    - keystone: keystone_service_tenant
+    - keystoneng: keystone_service_tenant
 
 {%- if not server.get('ldap', {}).get('read_only', False) %}
 
 keystone_admin_user:
-  keystone.user_present:
+  keystoneng.user_present:
   - name: {{ server.admin_name }}
   - password: {{ server.admin_password }}
   - email: {{ server.admin_email }}
@@ -375,8 +375,8 @@
   - connection_token: {{ server.service_token }}
   - connection_endpoint: 'http://{{ server.bind.address }}:{{ server.bind.private_port }}/v2.0'
   - require:
-    - keystone: keystone_admin_tenant
-    - keystone: keystone_roles
+    - keystoneng: keystone_admin_tenant
+    - keystoneng: keystone_roles
 
 {%- endif %}
 
@@ -385,17 +385,17 @@
 {%- for service_name, service in server.get('service', {}).iteritems() %}
 
 keystone_{{ service_name }}_service:
-  keystone.service_present:
+  keystoneng.service_present:
   - name: {{ service_name }}
   - service_type: {{ service.type }}
   - description: {{ service.description }}
   - connection_token: {{ server.service_token }}
   - connection_endpoint: 'http://{{ server.bind.address }}:{{ server.bind.private_port }}/v2.0'
   - require:
-    - keystone: keystone_roles
+    - keystoneng: keystone_roles
 
 keystone_{{ service_name }}_{{ service.get('region', 'RegionOne') }}_endpoint:
-  keystone.endpoint_present:
+  keystoneng.endpoint_present:
   - name: {{ service.get('service', service_name) }}
   - publicurl: '{{ service.bind.get('public_protocol', 'http') }}://{{ service.bind.public_address }}:{{ service.bind.public_port }}{{ service.bind.public_path }}'
   - internalurl: '{{ service.bind.get('internal_protocol', 'http') }}://{{ service.bind.internal_address }}:{{ service.bind.internal_port }}{{ service.bind.internal_path }}'
@@ -404,12 +404,12 @@
   - connection_token: {{ server.service_token }}
   - connection_endpoint: 'http://{{ server.bind.address }}:{{ server.bind.private_port }}/v2.0'
   - require:
-    - keystone: keystone_{{ service_name }}_service
+    - keystoneng: keystone_{{ service_name }}_service
 
 {% if service.user is defined %}
 
 keystone_user_{{ service.user.name }}:
-  keystone.user_present:
+  keystoneng.user_present:
   - name: {{ service.user.name }}
   - password: {{ service.user.password }}
   - email: {{ server.admin_email }}
@@ -420,7 +420,7 @@
   - connection_token: {{ server.service_token }}
   - connection_endpoint: 'http://{{ server.bind.address }}:{{ server.bind.private_port }}/v2.0'
   - require:
-    - keystone: keystone_roles
+    - keystoneng: keystone_roles
 
 {% endif %}
 
@@ -429,17 +429,17 @@
 {%- for tenant_name, tenant in server.get('tenant', {}).iteritems() %}
 
 keystone_tenant_{{ tenant_name }}:
-  keystone.tenant_present:
+  keystoneng.tenant_present:
   - name: {{ tenant_name }}
   - connection_token: {{ server.service_token }}
   - connection_endpoint: 'http://{{ server.bind.address }}:{{ server.bind.private_port }}/v2.0'
   - require:
-    - keystone: keystone_roles
+    - keystoneng: keystone_roles
 
 {%- for user_name, user in tenant.get('user', {}).iteritems() %}
 
 keystone_user_{{ user_name }}:
-  keystone.user_present:
+  keystoneng.user_present:
   - name: {{ user_name }}
   - password: {{ user.password }}
   - email: {{ user.get('email', 'root@localhost') }}
@@ -454,7 +454,7 @@
   - connection_token: {{ server.service_token }}
   - connection_endpoint: 'http://{{ server.bind.address }}:{{ server.bind.private_port }}/v2.0'
   - require:
-    - keystone: keystone_tenant_{{ tenant_name }}
+    - keystoneng: keystone_tenant_{{ tenant_name }}
 
 {%- endfor %}