Merge "Add role inference calls, domains"
diff --git a/_modules/keystonev3/__init__.py b/_modules/keystonev3/__init__.py
index 9581cb1..1f929b2 100644
--- a/_modules/keystonev3/__init__.py
+++ b/_modules/keystonev3/__init__.py
@@ -5,62 +5,117 @@
 except ImportError:
     REQUIREMENTS_MET = False
 
+from keystonev3 import lists
 from keystonev3 import endpoints
 from keystonev3 import roles
 from keystonev3 import services
 from keystonev3 import projects
 from keystonev3 import users
+from keystonev3 import domains
+from keystonev3 import regions
+from keystonev3 import groups
+
+
+domain_list = lists.domain_list
+domain_create = domains.domain_create
+domain_update = domains.domain_update
+domain_get_details = domains.domain_get_details
+domain_delete = domains.domain_delete
 
 endpoint_get_details = endpoints.endpoint_get_details
 endpoint_update = endpoints.endpoint_update
 endpoint_delete = endpoints.endpoint_delete
-endpoint_list = endpoints.endpoint_list
+endpoint_list = lists.endpoint_list
 endpoint_create = endpoints.endpoint_create
 
-role_assignment_list = roles.role_assignment_list
-role_assignment_check = roles.role_assignment_check
-role_add = roles.role_add
-role_delete = roles.role_delete
+role_assignment_list = lists.role_assignment_list
+role_assign_for_user_on_project = roles.role_assign_for_user_on_project
+role_assign_for_user_on_domain = roles.role_assign_for_user_on_domain
+role_unassign_for_user_on_project = roles.role_unassign_for_user_on_project
+role_unassign_for_user_on_domain = roles.role_unassign_for_user_on_domain
+role_assign_check_for_user_on_project = roles.\
+    role_assign_check_for_user_on_project
+role_assign_check_for_user_on_domain = roles.\
+    role_assign_check_for_user_on_domain
 role_get_details = roles.role_get_details
 role_update = roles.role_update
-role_delete = roles.role_delete
-role_list = roles.role_list
+role_list = lists.role_list
 role_create = roles.role_create
+role_delete = roles.role_delete
+
+role_inference_rule_list = lists.role_inference_rule_list
+role_inference_rule_for_role_list = roles.role_inference_rule_for_role_list
+role_inference_rule_create = roles.role_inference_rule_create
+role_inference_rule_get = roles.role_inference_rule_get
+role_inference_rule_confirm = roles.role_inference_rule_confirm
+role_inference_rule_delete = roles.role_inference_rule_delete
+
 
 service_get_details = services.service_get_details
 service_update = services.service_update
 service_delete = services.service_delete
-service_list = services.service_list
+service_list = lists.service_list
 service_create = services.service_create
 
 project_get_details = projects.project_get_details
 project_update = projects.project_update
 project_delete = projects.project_delete
-project_list = projects.project_list
+project_list = lists.project_list
 project_create = projects.project_create
 
 user_get_details = users.user_get_details
 user_update = users.user_update
 user_delete = users.user_delete
-user_list = users.user_list
+user_list = lists.user_list
 user_create = users.user_create
 
 
+region_list = lists.region_list
+region_create = regions.region_create
+region_get_details = regions.region_get_details
+region_update = regions.region_update
+region_delete = regions.region_delete
+
+group_list = lists.group_list
+group_create = groups.group_create
+group_get_details = groups.group_get_details
+group_update = groups.group_update
+group_delete = groups.group_delete
+group_user_list = groups.group_user_list
+group_user_add = groups.group_user_add
+group_user_check = groups.group_user_check
+group_user_remove = groups.group_user_remove
+
+
 __all__ = (
+    'domain_list',
+    'domain_create',
+    'domain_get_details',
+    'domain_update',
+    'domain_delete',
     'endpoint_get_details',
     'endpoint_update',
     'endpoint_delete',
     'endpoint_list',
     'endpoint_create',
     'role_assignment_list',
-    'role_assignment_check',
-    'role_add',
-    'role_delete',
+    'role_assign_for_user_on_project',
+    'role_assign_for_user_on_domain',
+    'role_assign_check_for_user_on_domain',
+    'role_assign_check_for_user_on_project',
+    'role_unassign_for_user_on_domain',
+    'role_unassign_for_user_on_project',
     'role_get_details',
     'role_update',
     'role_delete',
     'role_list',
     'role_create',
+    'role_inference_rule_confirm',
+    'role_inference_rule_create',
+    'role_inference_rule_delete',
+    'role_inference_rule_for_role_list',
+    'role_inference_rule_get',
+    'role_inference_rule_list',
     'service_get_details',
     'service_update',
     'service_delete',
@@ -75,7 +130,21 @@
     'user_update',
     'user_delete',
     'user_list',
-    'user_create'
+    'user_create',
+    'region_create',
+    'region_delete',
+    'region_get_details',
+    'region_list',
+    'region_update',
+    'group_list',
+    'group_create',
+    'group_delete',
+    'group_get_details',
+    'group_update',
+    'group_user_add',
+    'group_user_check',
+    'group_user_list',
+    'group_user_remove',
 )
 
 
diff --git a/_modules/keystonev3/arg_converter.py b/_modules/keystonev3/arg_converter.py
new file mode 100644
index 0000000..2ef40aa
--- /dev/null
+++ b/_modules/keystonev3/arg_converter.py
@@ -0,0 +1,73 @@
+import uuid
+from keystonev3 import common
+from keystonev3 import lists
+
+
+class CheckId(object):
+    def check_id(self, val):
+        try:
+            return str(uuid.UUID(val)).replace('-', '') == val
+        except (TypeError, ValueError, AttributeError):
+            return False
+
+
+class DomainCheckId(CheckId):
+    def check_id(self, val):
+        if val == 'default':
+            return True
+        return super(DomainCheckId, self).check_id(val)
+
+
+resource_lists = {
+    'project': lists.project_list,
+    'role': lists.role_list,
+    'service': lists.service_list,
+    'user': lists.user_list,
+    'domain': lists.domain_list,
+    'group': lists.group_list,
+}
+
+
+response_keys = {
+    'project': 'projects',
+    'role': 'roles',
+    'service': 'services',
+    'user': 'users',
+    'domain': 'domains',
+    'group': 'groups'
+}
+
+
+def get_by_name_or_uuid_multiple(resource_arg_name_pairs):
+    def wrap(func):
+        def wrapped_f(*args, **kwargs):
+            results = []
+            args_start = 0
+            for index, (resource, arg_name) in enumerate(
+                    resource_arg_name_pairs):
+                if arg_name in kwargs:
+                    ref = kwargs.pop(arg_name, None)
+                else:
+                    ref = args[index]
+                    args_start += 1
+                cloud_name = kwargs['cloud_name']
+                if resource == 'domain':
+                    checker = DomainCheckId()
+                else:
+                    checker = CheckId()
+                if checker.check_id(ref):
+                    results.append(ref)
+                else:
+                    # Then we have name not uuid
+                    resp_key = response_keys[resource]
+                    resp = resource_lists[resource](
+                        name=ref, cloud_name=cloud_name)[resp_key]
+                    if len(resp) == 0:
+                        raise common.ResourceNotFound(resp_key, ref)
+                    elif len(resp) > 1:
+                        raise common.MultipleResourcesFound(resp_key, ref)
+                    results.append(resp[0]['id'])
+                results.extend(args[args_start:])
+            return func(*results, **kwargs)
+        return wrapped_f
+    return wrap
\ No newline at end of file
diff --git a/_modules/keystonev3/common.py b/_modules/keystonev3/common.py
index 52b7914..93cfe6d 100644
--- a/_modules/keystonev3/common.py
+++ b/_modules/keystonev3/common.py
@@ -1,6 +1,5 @@
 import logging
 import os_client_config
-import uuid
 
 log = logging.getLogger(__name__)
 
@@ -96,36 +95,3 @@
             return resp
         return wrapped_f
     return wrap
-
-
-def _check_uuid(val):
-    try:
-        return str(uuid.UUID(val)).replace('-', '') == val
-    except (TypeError, ValueError, AttributeError):
-        return False
-
-
-def get_by_name_or_uuid(resource_list, resp_key, arg_name):
-    def wrap(func):
-        def wrapped_f(*args, **kwargs):
-            if arg_name in kwargs:
-                ref = kwargs.pop(arg_name, None)
-                start_arg = 0
-            else:
-                start_arg = 1
-                ref = args[0]
-            cloud_name = kwargs['cloud_name']
-            if _check_uuid(ref):
-                uuid = ref
-            else:
-                # Then we have name not uuid
-                resp = resource_list(
-                    name=ref, cloud_name=cloud_name)[resp_key]
-                if len(resp) == 0:
-                    raise ResourceNotFound(resp_key, ref)
-                elif len(resp) > 1:
-                    raise MultipleResourcesFound(resp_key, ref)
-                uuid = resp[0]['id']
-            return func(uuid, *args[start_arg:], **kwargs)
-        return wrapped_f
-    return wrap
diff --git a/_modules/keystonev3/domains.py b/_modules/keystonev3/domains.py
new file mode 100644
index 0000000..d594f05
--- /dev/null
+++ b/_modules/keystonev3/domains.py
@@ -0,0 +1,36 @@
+from keystonev3.common import send
+from keystonev3.arg_converter import get_by_name_or_uuid_multiple
+
+
+@send('post')
+def domain_create(name, **kwargs):
+    url = '/domains'
+    json = {
+        'domain': kwargs,
+    }
+    json['domain']['name'] = name
+    return url, json
+
+
+@get_by_name_or_uuid_multiple([('domain', 'domain_id')])
+@send('get')
+def domain_get_details(domain_id, **kwargs):
+    url = '/domains/{}'.format(domain_id)
+    return url, None
+
+
+@get_by_name_or_uuid_multiple([('domain', 'domain_id')])
+@send('patch')
+def domain_update(domain_id, **kwargs):
+    url = '/domains/{}'.format(domain_id)
+    json = {
+        'domain': kwargs,
+    }
+    return url, json
+
+
+@get_by_name_or_uuid_multiple([('domain', 'domain_id')])
+@send('delete')
+def domain_delete(domain_id, **kwargs):
+    url = '/domains/{}'.format(domain_id)
+    return url, None
diff --git a/_modules/keystonev3/endpoints.py b/_modules/keystonev3/endpoints.py
index 4230ad3..9f378d5 100644
--- a/_modules/keystonev3/endpoints.py
+++ b/_modules/keystonev3/endpoints.py
@@ -1,5 +1,5 @@
 from keystonev3.common import send
-
+from keystonev3.arg_converter import get_by_name_or_uuid_multiple
 try:
     from urllib.parse import urlencode
 except ImportError:
@@ -27,16 +27,16 @@
     return url, None
 
 
-@send('get')
-def endpoint_list(**kwargs):
-    url = '/endpoints?{}'.format(urlencode(kwargs))
-    return url, None
-
-
+@get_by_name_or_uuid_multiple([('service', 'service_id')])
 @send('post')
-def endpoint_create(**kwargs):
-    url = '/endpoints'
+def endpoint_create(service_id, url, interface, **kwargs):
+    api_url = '/endpoints'
     json = {
-        'endpoint': kwargs,
+        'endpoint': {
+            'service_id': service_id,
+            'url': url,
+            'interface': interface,
+        }
     }
-    return url, json
+    json['endpoint'].update(kwargs)
+    return api_url, json
diff --git a/_modules/keystonev3/groups.py b/_modules/keystonev3/groups.py
new file mode 100644
index 0000000..a8890d5
--- /dev/null
+++ b/_modules/keystonev3/groups.py
@@ -0,0 +1,71 @@
+from keystonev3.common import send
+from keystonev3.arg_converter import get_by_name_or_uuid_multiple
+try:
+    from urllib.parse import urlencode
+except ImportError:
+    from urllib import urlencode
+
+
+@get_by_name_or_uuid_multiple([('domain', 'domain_id')])
+@send('post')
+def group_create(domain_id, name, **kwargs):
+    url = '/groups'
+    json = {
+        'group':{
+            'name': name,
+            'domain_id': domain_id,
+        }
+    }
+    json['group'].update(kwargs)
+    return url, json
+
+
+@get_by_name_or_uuid_multiple([('group', 'group_id')])
+@send('get')
+def group_get_details(group_id, **kwargs):
+    url = '/groups/{}'.format(group_id)
+    return url, None
+
+
+@get_by_name_or_uuid_multiple([('group', 'group_id')])
+@send('patch')
+def group_update(group_id, **kwargs):
+    url = '/groups/{}'.format(group_id)
+    json = {
+        'group': kwargs,
+    }
+    return url, json
+
+
+@get_by_name_or_uuid_multiple([('group', 'group_id')])
+@send('delete')
+def group_delete(group_id, **kwargs):
+    url = '/groups/{}'.format(group_id)
+    return url, None
+
+@get_by_name_or_uuid_multiple([('group', 'group_id')])
+@send('get')
+def group_user_list(group_id, **kwargs):
+    url = '/groups/{}?{}'.format(group_id, urlencode(kwargs))
+    return url, None
+
+
+@get_by_name_or_uuid_multiple([('group', 'group_id'), ('user', 'user_id')])
+@send('put')
+def group_user_add(group_id, user_id, **kwargs):
+    url = '/groups/{}/users/{}'.format(group_id, user_id)
+    return url, None
+
+
+@get_by_name_or_uuid_multiple([('group', 'group_id'), ('user', 'user_id')])
+@send('head')
+def group_user_check(group_id, user_id, **kwargs):
+    url = '/groups/{}/users/{}'.format(group_id, user_id)
+    return url, None
+
+
+@get_by_name_or_uuid_multiple([('group', 'group_id'), ('user', 'user_id')])
+@send('delete')
+def group_user_remove(group_id, user_id, **kwargs):
+    url = '/groups/{}/users/{}'.format(group_id, user_id)
+    return url, None
diff --git a/_modules/keystonev3/lists.py b/_modules/keystonev3/lists.py
new file mode 100644
index 0000000..f2d8344
--- /dev/null
+++ b/_modules/keystonev3/lists.py
@@ -0,0 +1,66 @@
+from keystonev3.common import send
+
+try:
+    from urllib.parse import urlencode
+except ImportError:
+    from urllib import urlencode
+
+
+@send('get')
+def project_list(**kwargs):
+    url = '/projects?{}'.format(urlencode(kwargs))
+    return url, None
+
+
+@send('get')
+def domain_list(**kwargs):
+    url = '/domains?{}'.format(urlencode(kwargs))
+    return url, None
+
+
+@send('get')
+def endpoint_list(**kwargs):
+    url = '/endpoints?{}'.format(urlencode(kwargs))
+    return url, None
+
+
+@send('get')
+def service_list(**kwargs):
+    url = '/services?{}'.format(urlencode(kwargs))
+    return url, None
+
+
+@send('get')
+def user_list(**kwargs):
+    url = '/users?{}'.format(urlencode(kwargs))
+    return url, None
+
+
+@send('get')
+def role_list(**kwargs):
+    url = '/roles?{}'.format(urlencode(kwargs))
+    return url, None
+
+
+@send('get')
+def role_assignment_list(**kwargs):
+    url = '/role_assignments?{}'.format(urlencode(kwargs))
+    return url, None
+
+
+@send('get')
+def role_inference_rule_list(**kwargs):
+    url = '/role_inferences'
+    return url, None
+
+
+@send('get')
+def region_list(**kwargs):
+    url = '/regions?{}'.format(urlencode(kwargs))
+    return url, None
+
+
+@send('get')
+def group_list(**kwargs):
+    url = '/groups?{}'.format(urlencode(kwargs))
+    return url, None
\ No newline at end of file
diff --git a/_modules/keystonev3/projects.py b/_modules/keystonev3/projects.py
index 26e44fe..643a46d 100644
--- a/_modules/keystonev3/projects.py
+++ b/_modules/keystonev3/projects.py
@@ -1,24 +1,20 @@
-from keystonev3.common import get_by_name_or_uuid, send
+from keystonev3.common import send
+from keystonev3.arg_converter import get_by_name_or_uuid_multiple
+
 try:
     from urllib.parse import urlencode
 except ImportError:
     from urllib import urlencode
 
 
-@send('get')
-def project_list(**kwargs):
-    url = '/projects?{}'.format(urlencode(kwargs))
-    return url, None
-
-
-@get_by_name_or_uuid(project_list, 'projects', 'project_id')
+@get_by_name_or_uuid_multiple([('project', 'project_id')])
 @send('get')
 def project_get_details(project_id, **kwargs):
     url = '/projects/{}?{}'.format(project_id, urlencode(kwargs))
     return url, None
 
 
-@get_by_name_or_uuid(project_list, 'projects', 'project_id')
+@get_by_name_or_uuid_multiple([('project', 'project_id')])
 @send('patch')
 def project_update(project_id, **kwargs):
     url = '/projects/{}'.format(project_id)
@@ -28,17 +24,22 @@
     return url, json
 
 
-@get_by_name_or_uuid(project_list, 'projects', 'project_id')
+@get_by_name_or_uuid_multiple([('project', 'project_id')])
 @send('delete')
 def project_delete(project_id, **kwargs):
     url = '/projects/{}'.format(project_id)
     return url, None
 
 
+@get_by_name_or_uuid_multiple([('domain', 'domain_id')])
 @send('post')
-def project_create(**kwargs):
+def project_create(domain_id, name,**kwargs):
     url = '/projects'
     json = {
-        'project': kwargs,
+        'project': {
+            'name': name,
+            'domain_id': domain_id,
+        }
     }
+    json['project'].update(kwargs)
     return url, json
diff --git a/_modules/keystonev3/regions.py b/_modules/keystonev3/regions.py
new file mode 100644
index 0000000..549e27a
--- /dev/null
+++ b/_modules/keystonev3/regions.py
@@ -0,0 +1,31 @@
+from keystonev3.common import send
+
+
+@send('get')
+def region_get_details(region_id, **kwargs):
+    url = '/regions/{}'.format(region_id)
+    return url, None
+
+
+@send('patch')
+def region_update(region_id, **kwargs):
+    url = '/regions/{}'.format(region_id)
+    json = {
+        'region': kwargs
+    }
+    return url, json
+
+
+@send('delete')
+def region_delete(region_id, **kwargs):
+    url = '/regions/{}'.format(region_id)
+    return url, None
+
+
+@send('post')
+def region_create(**kwargs):
+    url = '/regions'
+    json = {
+        'region': kwargs
+    }
+    return url, json
\ No newline at end of file
diff --git a/_modules/keystonev3/roles.py b/_modules/keystonev3/roles.py
index bd85c16..3f6c06a 100644
--- a/_modules/keystonev3/roles.py
+++ b/_modules/keystonev3/roles.py
@@ -1,5 +1,6 @@
-from keystonev3.common import get_by_name_or_uuid, send
-from keystonev3.common import KeystoneException
+from keystonev3.common import send
+from keystonev3.arg_converter import get_by_name_or_uuid_multiple
+
 
 try:
     from urllib.parse import urlencode
@@ -7,69 +8,64 @@
     from urllib import urlencode
 
 
-@send('get')
-def role_list(**kwargs):
-    url = '/roles?{}'.format(urlencode(kwargs))
-    return url, None
-
-
-@send('get')
-def role_assignment_list(**kwargs):
-    url = '/role_assignments?{}'.format(urlencode(kwargs))
-    return url, None
-
-
+@get_by_name_or_uuid_multiple([('project', 'project_id'), ('user', 'user_id'),
+                               ('role', 'role_id')])
 @send('put')
-def role_add(user_id, role_id, project_id=None, domain_id=None, **kwargs):
-    if (project_id and domain_id) or (not project_id and not domain_id):
-        raise KeystoneException('Role can be assigned either to project '
-                                'or domain.')
-    if project_id:
-        url = '/projects/{}/users/{}/roles/{}'.format(project_id, user_id,
-                                                      role_id)
-    elif domain_id:
-        url = '/domains/{}/users/{}/roles/{}'.format(domain_id, user_id,
-                                                     role_id)
+def role_assign_for_user_on_project(project_id, user_id, role_id, **kwargs):
+    url = '/projects/{}/users/{}/roles/{}'.format(project_id, user_id, role_id)
     return url, None
 
 
+@get_by_name_or_uuid_multiple([('domain', 'domain_id'), ('user', 'user_id'),
+                               ('role', 'role_id')])
+@send('put')
+def role_assign_for_user_on_domain(domain_id, user_id, role_id, **kwargs):
+    url = '/domains/{}/users/{}/roles/{}'.format(domain_id, user_id, role_id)
+    return url, None
+
+
+@get_by_name_or_uuid_multiple([('project', 'project_id'), ('user', 'user_id'),
+                               ('role', 'role_id')])
 @send('delete')
-def role_delete(user_id, role_id, project_id=None, domain_id=None, **kwargs):
-    if (project_id and domain_id) or (not project_id and not domain_id):
-        raise KeystoneException('Role can be unassigned either from project '
-                                'or domain.')
-    if project_id:
-        url = '/projects/{}/users/{}/roles/{}'.format(project_id, user_id,
-                                                      role_id)
-    elif domain_id:
-        url = '/domains/{}/users/{}/roles/{}'.format(domain_id, user_id,
-                                                     role_id)
+def role_unassign_for_user_on_project(project_id, user_id, role_id, **kwargs):
+    url = '/projects/{}/users/{}/roles/{}'.format(project_id, user_id, role_id)
     return url, None
 
 
+@get_by_name_or_uuid_multiple([('domain', 'domain_id'), ('user', 'user_id'),
+                               ('role', 'role_id')])
+@send('delete')
+def role_unassign_for_user_on_domain(domain_id, user_id, role_id, **kwargs):
+    url = '/domains/{}/users/{}/roles/{}'.format(domain_id, user_id, role_id)
+    return url, None
+
+
+@get_by_name_or_uuid_multiple([('project', 'project_id'), ('user', 'user_id'),
+                               ('role', 'role_id')])
 @send('head')
-def role_assignment_check(user_id, role_id, project_id=None,
-                          domain_id=None, **kwargs):
-    if (project_id and domain_id) or (not project_id and not domain_id):
-        raise KeystoneException('Role can be assigned either to project '
-                                'or domain.')
-    if project_id:
-        url = '/projects/{}/users/{}/roles/{}'.format(project_id, user_id,
-                                                      role_id)
-    elif domain_id:
-        url = '/domains/{}/users/{}/roles/{}'.format(domain_id, user_id,
-                                                     role_id)
+def role_assign_check_for_user_on_project(project_id, user_id, role_id,
+                                          **kwargs):
+    url = '/projects/{}/users/{}/roles/{}'.format(project_id, user_id, role_id)
     return url, None
 
 
-@get_by_name_or_uuid(role_list, 'roles', 'role_id')
+@get_by_name_or_uuid_multiple([('domain', 'domain_id'), ('user', 'user_id'),
+                               ('role', 'role_id')])
+@send('head')
+def role_assign_check_for_user_on_domain(domain_id, user_id, role_id,
+                                         **kwargs):
+    url = '/domains/{}/users/{}/roles/{}'.format(domain_id, user_id, role_id)
+    return url, None
+
+
+@get_by_name_or_uuid_multiple([('role', 'role_id')])
 @send('get')
 def role_get_details(role_id, **kwargs):
     url = '/roles/{}?{}'.format(role_id, urlencode(kwargs))
     return url, None
 
 
-@get_by_name_or_uuid(role_list, 'roles', 'role_id')
+@get_by_name_or_uuid_multiple([('role', 'role_id')])
 @send('patch')
 def role_update(role_id, **kwargs):
     url = '/roles/{}'.format(role_id)
@@ -79,9 +75,9 @@
     return url, json
 
 
-@get_by_name_or_uuid(role_list, 'roles', 'role_id')
+@get_by_name_or_uuid_multiple([('role', 'role_id')])
 @send('delete')
-def role_remove(role_id, **kwargs):
+def role_delete(role_id, **kwargs):
     url = '/roles/{}'.format(role_id)
     return url, None
 
@@ -93,3 +89,42 @@
         'role': kwargs,
     }
     return url, json
+
+
+@get_by_name_or_uuid_multiple([('role', 'prior_role_id')])
+@send('get')
+def role_inference_rule_for_role_list(prior_role_id, **kwargs):
+    url = '/roles/{}/implies'.format(prior_role_id)
+    return url, None
+
+
+@get_by_name_or_uuid_multiple([('role', 'prior_role_id'),
+                               ('role', 'implies_role_id')])
+@send('put')
+def role_inference_rule_create(prior_role_id, implies_role_id, **kwargs):
+    url = '/roles/{}/implies/{}'.format(prior_role_id, implies_role_id)
+    return url, None
+
+
+@get_by_name_or_uuid_multiple([('role', 'prior_role_id'),
+                               ('role', 'implies_role_id')])
+@send('get')
+def role_inference_rule_get(prior_role_id, implies_role_id, **kwargs):
+    url = '/roles/{}/implies/{}'.format(prior_role_id, implies_role_id)
+    return url, None
+
+
+@get_by_name_or_uuid_multiple([('role', 'prior_role_id'),
+                               ('role', 'implies_role_id')])
+@send('head')
+def role_inference_rule_confirm(prior_role_id, implies_role_id, **kwargs):
+    url = '/roles/{}/implies/{}'.format(prior_role_id, implies_role_id)
+    return url, None
+
+
+@get_by_name_or_uuid_multiple([('role', 'prior_role_id'),
+                               ('role', 'implies_role_id')])
+@send('delete')
+def role_inference_rule_delete(prior_role_id, implies_role_id, **kwargs):
+    url = '/roles/{}/implies/{}'.format(prior_role_id, implies_role_id)
+    return url, None
diff --git a/_modules/keystonev3/services.py b/_modules/keystonev3/services.py
index f917cbf..546e3a0 100644
--- a/_modules/keystonev3/services.py
+++ b/_modules/keystonev3/services.py
@@ -1,4 +1,5 @@
-from keystonev3.common import get_by_name_or_uuid, send
+from keystonev3.common import send
+from keystonev3.arg_converter import get_by_name_or_uuid_multiple
 
 try:
     from urllib.parse import urlencode
@@ -6,20 +7,14 @@
     from urllib import urlencode
 
 
-@send('get')
-def service_list(**kwargs):
-    url = '/services?{}'.format(urlencode(kwargs))
-    return url, None
-
-
-@get_by_name_or_uuid(service_list, 'services', 'service_id')
+@get_by_name_or_uuid_multiple([('service', 'service_id')])
 @send('get')
 def service_get_details(service_id, **kwargs):
     url = '/services/{}?{}'.format(service_id, urlencode(kwargs))
     return url, None
 
 
-@get_by_name_or_uuid(service_list, 'services', 'service_id')
+@get_by_name_or_uuid_multiple([('service', 'service_id')])
 @send('patch')
 def service_update(service_id, **kwargs):
     url = '/services/{}'.format(service_id)
@@ -29,7 +24,7 @@
     return url, json
 
 
-@get_by_name_or_uuid(service_list, 'services', 'service_id')
+@get_by_name_or_uuid_multiple([('service', 'service_id')])
 @send('delete')
 def service_delete(service_id, **kwargs):
     url = '/services/{}'.format(service_id)
diff --git a/_modules/keystonev3/users.py b/_modules/keystonev3/users.py
index 9582eef..d239922 100644
--- a/_modules/keystonev3/users.py
+++ b/_modules/keystonev3/users.py
@@ -1,24 +1,19 @@
-from keystonev3.common import get_by_name_or_uuid, send
+from keystonev3.common import send
+from keystonev3.arg_converter import get_by_name_or_uuid_multiple
 try:
     from urllib.parse import urlencode
 except ImportError:
     from urllib import urlencode
 
 
-@send('get')
-def user_list(**kwargs):
-    url = '/users?{}'.format(urlencode(kwargs))
-    return url, None
-
-
-@get_by_name_or_uuid(user_list, 'users', 'user_id')
+@get_by_name_or_uuid_multiple([('user', 'user_id')])
 @send('get')
 def user_get_details(user_id, **kwargs):
     url = '/users/{}?{}'.format(user_id, urlencode(kwargs))
     return url, None
 
 
-@get_by_name_or_uuid(user_list, 'users', 'user_id')
+@get_by_name_or_uuid_multiple([('user', 'user_id')])
 @send('patch')
 def user_update(user_id, **kwargs):
     url = '/users/{}'.format(user_id)
@@ -28,7 +23,7 @@
     return url, json
 
 
-@get_by_name_or_uuid(user_list, 'users', 'user_id')
+@get_by_name_or_uuid_multiple([('user', 'user_id')])
 @send('delete')
 def user_delete(user_id, **kwargs):
     url = '/users/{}'.format(user_id)
diff --git a/_states/keystonev3.py b/_states/keystonev3.py
index 4a63d60..f9012cf 100644
--- a/_states/keystonev3.py
+++ b/_states/keystonev3.py
@@ -62,10 +62,14 @@
         return _find_failed(name, 'endpoint')
 
 
-def endpoint_absent(name, cloud_name):
+def endpoint_absent(name, service_id, interface, cloud_name):
+    service_id = _keystonev3_call(
+        'service_get_details', service_id,
+        cloud_name=cloud_name)['service']['id']
+
     endpoints = _keystonev3_call(
-        'endpoint_list', name=name, cloud_name=cloud_name
-    )['endpoints']
+        'endpoint_list', name=name, service_id=service_id, interface=interface,
+        cloud_name=cloud_name)['endpoints']
     if not endpoints:
         return _absent(name, 'endpoint')
     elif len(endpoints) == 1:
@@ -133,16 +137,36 @@
     return _find_failed(name, 'service')
 
 
-def project_present(name, cloud_name, **kwargs):
+def service_absent(name, cloud_name):
+    try:
+        _keystonev3_call(
+            'service_get_details', name,
+            cloud_name=cloud_name)['service']
+    except Exception as e:
+        if 'ResourceNotFound' in repr(e):
+            return _absent(name, 'service')
+        else:
+            log.error('Failed to get service {}'.format(e))
+            return _find_failed(name, 'service')
+    try:
+        _keystonev3_call('service_delete', name, cloud_name=cloud_name)
+    except Exception:
+        return _delete_failed(name, 'service')
+    return _deleted(name, 'service')
+
+
+
+def project_present(name, domain_id, cloud_name, **kwargs):
 
     projects = _keystonev3_call(
-        'project_list', name=name, cloud_name=cloud_name
+        'project_list', name=name, domain_id=domain_id, cloud_name=cloud_name
     )['projects']
 
     if not projects:
         try:
             resp = _keystonev3_call(
-                'project_create', name=name, cloud_name=cloud_name, **kwargs
+                'project_create', domain_id=domain_id, name=name,
+                cloud_name=cloud_name, **kwargs
             )
         except Exception as e:
             log.error('Keystone project create failed with {}'.format(e))
@@ -178,6 +202,24 @@
         return _find_failed(name, 'project')
 
 
+def project_absent(name, cloud_name):
+    try:
+        _keystonev3_call('project_get_details',
+                         project_id=name, cloud_name=cloud_name)
+    except Exception as e:
+        if 'ResourceNotFound' in repr(e):
+            return _absent(name, 'project')
+        else:
+            log.error('Failed to get project {}'.format(e))
+            return _find_failed(name, 'project')
+    try:
+        _keystonev3_call('project_delete', project_id=name,
+                         cloud_name=cloud_name)
+    except Exception:
+        return _delete_failed(name, 'project')
+    return _deleted(name, 'project')
+
+
 def user_present(name, cloud_name, password_reset=False, **kwargs):
 
     users = _keystonev3_call(
@@ -232,6 +274,23 @@
         return _find_failed(name, 'user')
 
 
+def user_absent(name, cloud_name):
+    try:
+        _keystonev3_call('user_get_details', user_id=name,
+                         cloud_name=cloud_name)
+    except Exception as e:
+        if 'ResourceNotFound' in repr(e):
+            return _absent(name, 'user')
+        else:
+            log.error('Failed to get user {}'.format(e))
+            return _find_failed(name, 'user')
+    try:
+        _keystonev3_call('user_delete', user_id=name, cloud_name=cloud_name)
+    except Exception:
+        return _delete_failed(name, 'user')
+    return _deleted(name, 'user')
+
+
 def user_role_assigned(name, role_id, cloud_name, project_id=None,
                        domain_id=None, role_domain_id=None, **kwargs):
 
@@ -244,21 +303,30 @@
                  'project_get_details', project_id,
                  cloud_name=cloud_name)['project']['id']
 
-# TODO: Add when domain support is added.
-#    if domain_id and not uuidutils.is_uuid_like(domain_id):
-#        domain_id  = _keystonev3_call(
-#                 'domain_get_details', domain_id,
-#                 cloud_name=cloud_name)['domain']['id']
+    if domain_id:
+        domain_id  = _keystonev3_call(
+            'domain_get_details', domain_id,
+            cloud_name=cloud_name)['domain']['id']
 
-#    if role_domain_id and not uuidutils.is_uuid_like(role_domain_id):
-#        role_domain_id  = _keystonev3_call(
-#                 'domain_get_details', role_domain_id,
-#                 cloud_name=cloud_name)['domain']['id']
+
+    if (project_id and domain_id) or (not project_id and not domain_id):
+        return {
+            'name': name,
+            'changes': {},
+            'result': False,
+            'comment': 'Use project_id or domain_id (only one of them)'
+        }
+
+
+    if role_domain_id:
+        role_domain_id  = _keystonev3_call(
+            'domain_get_details', role_domain_id,
+            cloud_name=cloud_name)['domain']['id']
 
     if role_id:
         role_id = _keystonev3_call(
-                 'role_get_details', role_id, domain_id=role_domain_id,
-                 cloud_name=cloud_name)['role']['id']
+            'role_get_details', role_id, domain_id=role_domain_id,
+            cloud_name=cloud_name)['role']['id']
 
     req_kwargs = {'role.id': role_id, 'user.id': user_id,
                   'cloud_name': cloud_name}
@@ -278,8 +346,10 @@
         req_kwargs['project_id'] = project_id
 
     if not role_assignments:
+        method_type = 'project' if project_id else 'domain'
+        method = 'role_assign_for_user_on_{}'.format(method_type)
         try:
-            resp = _keystonev3_call('role_add', **req_kwargs)
+            resp = _keystonev3_call(method, **req_kwargs)
         except Exception as e:
             log.error('Keystone user role assignment with {}'.format(e))
             return _create_failed(name, 'user_role_assignment')
@@ -289,6 +359,69 @@
     return _no_changes(name, 'user_role_assignment')
 
 
+def user_role_unassign(name, role_id, cloud_name, project_id=None,
+                       domain_id=None, role_domain_id=None):
+    user_id = _keystonev3_call(
+        'user_get_details', name,
+        cloud_name=cloud_name)['user']['id']
+
+    if project_id:
+        project_id = _keystonev3_call(
+            'project_get_details', project_id,
+            cloud_name=cloud_name)['project']['id']
+
+    if domain_id:
+        domain_id = _keystonev3_call(
+            'domain_get_details', domain_id,
+            cloud_name=cloud_name)['domain']['id']
+
+    if (project_id and domain_id) or (not project_id and not domain_id):
+        return {
+            'name': name,
+            'changes': {},
+            'result': False,
+            'comment': 'Use project_id or domain_id (only one of them)'
+        }
+
+    if role_domain_id:
+        role_domain_id = _keystonev3_call(
+            'domain_get_details', role_domain_id,
+            cloud_name=cloud_name)['domain']['id']
+
+    if role_id:
+        role_id = _keystonev3_call(
+            'role_get_details', role_id, domain_id=role_domain_id,
+            cloud_name=cloud_name)['role']['id']
+
+    req_kwargs = {'role.id': role_id, 'user.id': user_id,
+                  'cloud_name': cloud_name}
+    if domain_id:
+        req_kwargs['domain_id'] = domain_id
+    if project_id:
+        req_kwargs['project_id'] = project_id
+
+    role_assignments = _keystonev3_call(
+        'role_assignment_list', **req_kwargs)['role_assignments']
+
+    req_kwargs = {'cloud_name': cloud_name, 'user_id': user_id,
+                  'role_id': role_id}
+    if domain_id:
+        req_kwargs['domain_id'] = domain_id
+    if project_id:
+        req_kwargs['project_id'] = project_id
+
+    if not role_assignments:
+        return _absent(name, 'user_role_assignment')
+    else:
+        method_type = 'project' if project_id else 'domain'
+        method = 'role_unassign_for_user_on_{}'.format(method_type)
+        try:
+            _keystonev3_call(method, **req_kwargs)
+        except Exception:
+            return _delete_failed(name, 'user_role_assignment')
+    return _deleted(name, 'user_role_assignment')
+
+
 def role_present(name, cloud_name, **kwargs):
 
     roles = _keystonev3_call(
@@ -336,6 +469,23 @@
         return _find_failed(name, 'role')
 
 
+def role_absent(name, cloud_name):
+    try:
+        _keystonev3_call('role_get_details', role_id=name,
+                         cloud_name=cloud_name)
+    except Exception as e:
+        if 'ResourceNotFound' in repr(e):
+            return _absent(name, 'role')
+        else:
+            log.error('Failed to get role {}'.format(e))
+            return _find_failed(name, 'role')
+    try:
+        _keystonev3_call('role_delete', role_id=name, cloud_name=cloud_name)
+    except Exception:
+        return _delete_failed(name, 'role')
+    return _deleted(name, 'role')
+
+
 def _created(name, resource, resource_definition):
     changes_dict = {
         'name': name,
diff --git a/keystone/client/resources/v3.sls b/keystone/client/resources/v3.sls
index 015fdcf..ea174b2 100644
--- a/keystone/client/resources/v3.sls
+++ b/keystone/client/resources/v3.sls
@@ -46,7 +46,7 @@
   - url: {{ endpoint.url }}
   - interface: {{ endpoint.interface }}
   - service_id: {{ service_name }}
-  - region: {{ endpoint.region }}
+  - region_id: {{ endpoint.region }}
   - require:
     - keystone_service_{{ service_name }}_{{ service.type }}
 
@@ -61,16 +61,13 @@
   keystonev3.project_present:
   - cloud_name: {{ project.get('cloud_name', resources.cloud_name) }}
   - name: {{ project_name }}
+  - domain_id: {{ project.get('domain_id', 'default')}}
   {%- if project.is_domain is defined %}
   - is_domain: {{ project.is_domain }}
   {%- endif %}
   {%- if project.description is defined %}
   - description: {{ project.description }}
   {%- endif %}
-{# TODO unkomment when domain support is added. #}
-{#  {- if project.domain_id is defined %} #}
-{#  - domain_id: {{ project.domain_id }} #}
-{#  {%- endif %} #}
   {%- if project.enabled is defined %}
   - enabled: {{ project.enabled }}
   {%- endif %}