Add role inference calls, domains

Also rework get_by_name_or_uuid method to work with multiple keys
and add get_by_name_or_uuid decorator for role assign, unassgin and check.

Change-Id: I808ce0c1aa1a7231875256ae6f22e986d78f8f00
Related-Issue: PROD-21388
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)