diff --git a/tests/basic_deployment.py b/tests/basic_deployment.py
index b8dae2cf96a2f931ef6d0400bc8052e3b081f759..2f01247f2379fd560aaad8519f148e563e9a8e79 100644
--- a/tests/basic_deployment.py
+++ b/tests/basic_deployment.py
@@ -48,8 +48,8 @@ class KeystoneBasicDeployment(OpenStackAmuletDeployment):
         """Deploy the entire test environment."""
         super(KeystoneBasicDeployment, self).__init__(series, openstack,
                                                       source, stable)
-        self.keystone_num_units = 3
-        self.keystone_api_version = 2
+
+        self._initialize_deployment_differences()
 
         self._setup_test_object(snap_source)
         self._add_services()
@@ -63,6 +63,11 @@ class KeystoneBasicDeployment(OpenStackAmuletDeployment):
 
         self.d.sentry.wait()
         self._initialize_tests()
+        self._initialize_test_differences()
+
+    def _initialize_deployment_differences(self):
+        self.keystone_num_units = 3
+        self.keystone_api_version = 2
 
     def _setup_test_object(self, snap_source):
         self.snap_source = snap_source
@@ -181,22 +186,23 @@ class KeystoneBasicDeployment(OpenStackAmuletDeployment):
 
     def set_api_version(self, api_version):
         # Avoid costly settings if we are already at the correct api_version
-        if not self.api_change_required(api_version):
-            return True
-        u.log.debug('Setting preferred-api-version={}'.format(api_version))
-        se_rels = []
-        for i in range(0, self.keystone_num_units):
-            se_rels.append(
-                (self.keystone_sentries[i], 'cinder:identity-service'),
-            )
-        # Make config change, wait for propagation
-        u.keystone_configure_api_version(se_rels, self, api_version)
+        if self.api_change_required(api_version):
+            u.log.debug('Setting preferred-api-version={}'.format(api_version))
+            se_rels = []
+            for i in range(0, self.keystone_num_units):
+                se_rels.append(
+                    (self.keystone_sentries[i], 'cinder:identity-service'),
+                )
+            # Make config change, wait for propagation
+            u.keystone_configure_api_version(se_rels, self, api_version)
 
-        # Success if we get here, get and store client.
+        # Store in self.keystone_client
         if api_version == 2:
             self.keystone_v2 = self.get_keystone_client(api_version=2)
+            self.keystone_client = self.keystone_v2
         else:
             self.keystone_v3 = self.get_keystone_client(api_version=3)
+            self.keystone_client = self.keystone_v3
         self.keystone_api_version = api_version
 
     def get_keystone_client(self, api_version=None, keystone_ip=None):
@@ -221,41 +227,42 @@ class KeystoneBasicDeployment(OpenStackAmuletDeployment):
         self.demo_tenant = 'demoTenant'
         self.demo_role = 'demoRole'
         self.demo_user = 'demoUser'
-        if not u.tenant_exists(self.keystone_v2, self.demo_tenant):
-            tenant = self.keystone_v2.tenants.create(
+        if not u.tenant_exists(self.keystone_client, self.demo_tenant):
+            tenant = self.keystone_client.tenants.create(
                 tenant_name=self.demo_tenant,
                 description='demo tenant',
                 enabled=True)
-            self.keystone_v2.roles.create(name=self.demo_role)
-            self.keystone_v2.users.create(name=self.demo_user,
-                                          password='password',
-                                          tenant_id=tenant.id,
-                                          email='demo@demo.com')
+            self.keystone_client.roles.create(name=self.demo_role)
+            self.keystone_client.users.create(name=self.demo_user,
+                                              password='password',
+                                              tenant_id=tenant.id,
+                                              email='demo@demo.com')
 
         # Authenticate keystone demo
         self.keystone_demo = u.authenticate_keystone_user(
-            self.keystone_v2, user=self.demo_user,
+            self.keystone_client, user=self.demo_user,
             password='password', tenant=self.demo_tenant)
 
     def create_users_v3(self):
         # Create a demo tenant/role/user
         self.demo_project = 'demoProject'
         self.demo_user_v3 = 'demoUserV3'
+        self.demo_role = 'demoRoleV3'
         self.demo_domain_admin = 'demoDomainAdminV3'
         self.demo_domain = 'demoDomain'
         try:
-            domain = self.keystone_v3.domains.find(name=self.demo_domain)
+            domain = self.keystone_client.domains.find(name=self.demo_domain)
         except keystoneclient.exceptions.NotFound:
-            domain = self.keystone_v3.domains.create(
+            domain = self.keystone_client.domains.create(
                 self.demo_domain,
                 description='Demo Domain',
                 enabled=True
             )
 
         try:
-            self.keystone_v3.projects.find(name=self.demo_project)
+            self.keystone_client.projects.find(name=self.demo_project)
         except keystoneclient.exceptions.NotFound:
-            self.keystone_v3.projects.create(
+            self.keystone_client.projects.create(
                 self.demo_project,
                 domain,
                 description='Demo Project',
@@ -263,14 +270,14 @@ class KeystoneBasicDeployment(OpenStackAmuletDeployment):
             )
 
         try:
-            self.keystone_v3.roles.find(name=self.demo_role)
+            self.keystone_client.roles.find(name=self.demo_role)
         except keystoneclient.exceptions.NotFound:
-            self.keystone_v3.roles.create(name=self.demo_role)
+            self.keystone_client.roles.create(name=self.demo_role)
 
-        if not self.find_keystone_v3_user(self.keystone_v3,
+        if not self.find_keystone_v3_user(self.keystone_client,
                                           self.demo_user_v3,
                                           self.demo_domain):
-            self.keystone_v3.users.create(
+            self.keystone_client.users.create(
                 self.demo_user_v3,
                 domain=domain.id,
                 project=self.demo_project,
@@ -280,14 +287,14 @@ class KeystoneBasicDeployment(OpenStackAmuletDeployment):
                 enabled=True)
 
         try:
-            self.keystone_v3.roles.find(name='Admin')
+            self.keystone_client.roles.find(name='Admin')
         except keystoneclient.exceptions.NotFound:
-            self.keystone_v3.roles.create(name='Admin')
+            self.keystone_client.roles.create(name='Admin')
 
-        if not self.find_keystone_v3_user(self.keystone_v3,
+        if not self.find_keystone_v3_user(self.keystone_client,
                                           self.demo_domain_admin,
                                           self.demo_domain):
-            user = self.keystone_v3.users.create(
+            user = self.keystone_client.users.create(
                 self.demo_domain_admin,
                 domain=domain.id,
                 project=self.demo_project,
@@ -296,10 +303,10 @@ class KeystoneBasicDeployment(OpenStackAmuletDeployment):
                 description='Demo Admin',
                 enabled=True)
 
-            role = self.keystone_v3.roles.find(name='Admin')
-            u.log.debug("self.keystone_v3.roles.grant('{}', user='{}', "
+            role = self.keystone_client.roles.find(name='Admin')
+            u.log.debug("self.keystone_client.roles.grant('{}', user='{}', "
                         "domain='{}')".format(role.id, user.id, domain.id))
-            self.keystone_v3.roles.grant(
+            self.keystone_client.roles.grant(
                 role.id,
                 user=user.id,
                 domain=domain.id)
@@ -319,6 +326,8 @@ class KeystoneBasicDeployment(OpenStackAmuletDeployment):
         self.keystone_ip = self.keystone_sentries[0].relation(
             'shared-db',
             'percona-cluster:shared-db')['private-address']
+
+    def _initialize_test_differences(self):
         self.set_api_version(2)
         self.create_users_v2()
 
@@ -369,7 +378,7 @@ class KeystoneBasicDeployment(OpenStackAmuletDeployment):
 
     def test_102_keystone_tenants(self):
         self.set_api_version(2)
-        self.validate_keystone_tenants(self.keystone_v2)
+        self.validate_keystone_tenants(self.keystone_client)
 
     def validate_keystone_roles(self, client):
         """Verify all existing roles."""
@@ -388,7 +397,7 @@ class KeystoneBasicDeployment(OpenStackAmuletDeployment):
 
     def test_104_keystone_roles(self):
         self.set_api_version(2)
-        self.validate_keystone_roles(self.keystone_v2)
+        self.validate_keystone_roles(self.keystone_client)
 
     def validate_keystone_users(self, client):
         """Verify all existing roles."""
@@ -444,7 +453,7 @@ class KeystoneBasicDeployment(OpenStackAmuletDeployment):
 
     def test_106_keystone_users(self):
         self.set_api_version(2)
-        self.validate_keystone_users(self.keystone_v2)
+        self.validate_keystone_users(self.keystone_client)
 
     def is_liberty_or_newer(self):
         # os_release = self._get_openstack_release_string()
@@ -469,15 +478,15 @@ class KeystoneBasicDeployment(OpenStackAmuletDeployment):
     def test_112_keystone_list_resources(self):
         if self.is_mitaka_or_newer():
             self.set_api_version(3)
-            self.validate_keystone_tenants(self.keystone_v3)
-            self.validate_keystone_roles(self.keystone_v3)
-            self.validate_keystone_users(self.keystone_v3)
+            self.validate_keystone_tenants(self.keystone_client)
+            self.validate_keystone_roles(self.keystone_client)
+            self.validate_keystone_users(self.keystone_client)
 
     def test_118_keystone_create_users(self):
         if self.is_mitaka_or_newer():
             self.set_api_version(3)
             self.create_users_v3()
-            actual_user = self.find_keystone_v3_user(self.keystone_v3,
+            actual_user = self.find_keystone_v3_user(self.keystone_client,
                                                      self.demo_user_v3,
                                                      self.demo_domain)
             assert actual_user is not None
@@ -498,7 +507,7 @@ class KeystoneBasicDeployment(OpenStackAmuletDeployment):
         if self.is_mitaka_or_newer():
             self.set_api_version(3)
             self.create_users_v3()
-            actual_domain = self.keystone_v3.domains.find(
+            actual_domain = self.keystone_client.domains.find(
                 name=self.demo_domain
             )
             expect = {
@@ -597,7 +606,8 @@ class KeystoneBasicDeployment(OpenStackAmuletDeployment):
     def test_140_keystone_endpoint(self):
         """Verify the keystone endpoint data."""
         u.log.debug('Checking keystone api endpoint data...')
-        endpoints = self.keystone_v2.endpoints.list()
+        self.set_api_version(2)
+        endpoints = self.keystone_client.endpoints.list()
         admin_port = '35357'
         internal_port = public_port = '5000'
         expected = {
@@ -617,7 +627,7 @@ class KeystoneBasicDeployment(OpenStackAmuletDeployment):
     def test_142_cinder_endpoint(self):
         """Verify the cinder endpoint data."""
         u.log.debug('Checking cinder endpoint...')
-        endpoints = self.keystone_v2.endpoints.list()
+        endpoints = self.keystone_client.endpoints.list()
         admin_port = internal_port = public_port = '8776'
         expected = {
             'id': u.not_null,
@@ -947,11 +957,11 @@ class KeystoneBasicDeployment(OpenStackAmuletDeployment):
                 message="Unit is ready",
                 timeout=timeout,
                 include_only=['keystone'])
-            domain = self.keystone_v3.domains.find(name='admin_domain')
-            v3_admin_user = self.keystone_v3.users.list(domain=domain)[0]
+            domain = self.keystone_client.domains.find(name='admin_domain')
+            v3_admin_user = self.keystone_client.users.list(domain=domain)[0]
             u.log.debug(v3_admin_user)
-            self.keystone_v3.users.update(user=v3_admin_user,
-                                          password='wrongpass')
+            self.keystone_client.users.update(user=v3_admin_user,
+                                              password='wrongpass')
             u.log.debug('Removing keystone percona-cluster relation')
             self.d.unrelate('keystone:shared-db', 'percona-cluster:shared-db')
             self.d.sentry.wait(timeout=timeout)
@@ -976,3 +986,179 @@ class KeystoneBasicDeployment(OpenStackAmuletDeployment):
                     amulet.FAIL,
                     msg="Admin user password not reset")
             u.log.debug('OK')
+
+
+class KeystoneV3Deployment(KeystoneBasicDeployment):
+    """Amulet tests on a basic keystone deployment."""
+
+    def _initialize_deployment_differences(self):
+        self.keystone_num_units = 3
+        self.keystone_api_version = 3
+
+    def _initialize_test_differences(self):
+        self.keystone_client = self.get_keystone_client(api_version=3)
+        self.create_users_v3()
+
+    def api_change_required(self, api_version):
+        u.log.warn('This is a Keystone V3 only deployment.')
+        return False
+
+    def set_api_version(self, api_version):
+        u.log.warn('This is a Keystone V3 only deployment. '
+                   'Ignoring request for api version 2')
+
+    def validate_keystone_tenants(self, client):
+        """Verify all existing tenants."""
+        u.log.debug('Checking keystone tenants...')
+        expected = [
+            {'name': 'services',
+             'enabled': True,
+             'description': 'Created by Juju',
+             'id': u.not_null},
+            {'name': 'demoProject',
+             'enabled': True,
+             'description': 'Demo Project',
+             'id': u.not_null},
+            {'name': 'admin',
+             'enabled': True,
+             'description': 'Created by Juju',
+             'id': u.not_null}
+        ]
+        actual = client.projects.list()
+
+        ret = u.validate_tenant_data(expected, actual)
+        if ret:
+            amulet.raise_status(amulet.FAIL, msg=ret)
+
+    def validate_keystone_roles(self, client):
+        """Verify all existing roles."""
+        u.log.debug('Checking keystone roles...')
+        expected = [
+            {'name': 'demoRoleV3',
+             'id': u.not_null},
+            {'name': 'Admin',
+             'id': u.not_null}
+        ]
+        actual = client.roles.list()
+
+        ret = u.validate_role_data(expected, actual)
+        if ret:
+            amulet.raise_status(amulet.FAIL, msg=ret)
+
+    def validate_keystone_users(self, client):
+        """Verify all existing roles."""
+        u.log.debug('Checking keystone users...')
+
+        if self._get_openstack_release() < self.xenial_pike:
+            cinder_user = 'cinder_cinderv2'
+        else:
+            cinder_user = 'cinderv2_cinderv3'
+        base = [
+            {'name': 'demoUserV3',
+             'enabled': True,
+             'id': u.not_null,
+             'email': 'demov3@demo.com'},
+            {'name': 'admin',
+             'enabled': True,
+             'id': u.not_null,
+             'email': 'juju@localhost'},
+            {'name': cinder_user,
+             'enabled': True,
+             'id': u.not_null,
+             'email': u'juju@localhost'}
+        ]
+        expected = []
+        for user_info in base:
+            user_info['default_project_id'] = u.not_null
+            expected.append(user_info)
+        # Ensure list is scoped to the default domain
+        # when checking v3 users (v2->v3 upgrade check)
+        actual = client.users.list(
+            domain=client.domains.find(name=self.DEFAULT_DOMAIN).id
+        )
+        actual += client.users.list(
+            domain=client.domains.find(name=self.demo_domain).id)
+        actual += client.users.list(
+            domain=client.domains.find(name='admin_domain').id)
+        ret = u.validate_user_data(expected, actual,
+                                   api_version=self.keystone_api_version)
+        if ret:
+            amulet.raise_status(amulet.FAIL, msg=ret)
+
+    def test_138_service_catalog(self):
+        """Verify that the service catalog endpoint data is valid."""
+        u.log.debug('Checking keystone service catalog...')
+        expected = {
+            u'identity': [{u'id': u.not_null,
+                           u'interface': u'admin',
+                           u'region': u'RegionOne',
+                           u'region_id': u'RegionOne',
+                           u'url': u.valid_url},
+                          {u'id': u.not_null,
+                           u'interface': u'public',
+                           u'region': u'RegionOne',
+                           u'region_id': u'RegionOne',
+                           u'url': u.valid_url},
+                          {u'id': u.not_null,
+                           u'interface': u'internal',
+                           u'region': u'RegionOne',
+                           u'region_id': u'RegionOne',
+                           u'url': u.valid_url}],
+
+            u'volumev2': [{u'id': u.not_null,
+                           u'interface': u'admin',
+                           u'region': u'RegionOne',
+                           u'region_id': u'RegionOne',
+                           u'url': u.valid_url},
+                          {u'id': u.not_null,
+                           u'interface': u'public',
+                           u'region': u'RegionOne',
+                           u'region_id': u'RegionOne',
+                           u'url': u.valid_url},
+                          {u'id': u.not_null,
+                           u'interface': u'internal',
+                           u'region': u'RegionOne',
+                           u'region_id': u'RegionOne',
+                           u'url': u.valid_url}]}
+
+        actual = self.keystone_client.service_catalog.get_endpoints()
+        ret = u.validate_v3_svc_catalog_endpoint_data(expected, actual)
+        if ret:
+            amulet.raise_status(amulet.FAIL, msg=ret)
+
+    def test_140_keystone_endpoint(self):
+        """Verify the keystone endpoint data."""
+        u.log.debug('Checking keystone api endpoint data...')
+        admin_port = '35357'
+        internal_port = public_port = '5000'
+        expected = {'id': u.not_null,
+                    'region': 'RegionOne',
+                    'region_id': 'RegionOne',
+                    'interface': u.not_null,
+                    'url': u.valid_url,
+                    'service_id': u.not_null}
+
+        endpoints = self.keystone_client.endpoints.list()
+        ret = u.validate_v3_endpoint_data(endpoints, admin_port, internal_port,
+                                          public_port, expected)
+        if ret:
+            amulet.raise_status(amulet.FAIL,
+                                msg='keystone endpoint: {}'.format(ret))
+
+    def test_142_cinder_endpoint(self):
+        """Verify the cinder endpoint data."""
+        u.log.debug('Checking cinder endpoint...')
+        admin_port = internal_port = public_port = '8776'
+        expected = {'id': u.not_null,
+                    'region': 'RegionOne',
+                    'region_id': 'RegionOne',
+                    'interface': u.not_null,
+                    'url': u.valid_url,
+                    'service_id': u.not_null}
+        endpoints = self.keystone_client.endpoints.list()
+        ret = u.validate_v3_endpoint_data(endpoints, admin_port, internal_port,
+                                          public_port, expected,
+                                          expected_num_eps=6)
+        if ret:
+            amulet.raise_status(amulet.FAIL,
+                                msg='cinder endpoint: {}'.format(ret))
diff --git a/tests/dev-basic-bionic-queens b/tests/dev-basic-bionic-queens
index ae1dd5aa16554b3d2a9c7840af554ac4350d59b6..df46adc8c5f979d82f7a81f19d60d04e34043eb9 100755
--- a/tests/dev-basic-bionic-queens
+++ b/tests/dev-basic-bionic-queens
@@ -16,8 +16,8 @@
 
 """Amulet tests on a basic keystone deployment on bionic-queens."""
 
-from basic_deployment import KeystoneBasicDeployment
+from basic_deployment import KeystoneV3Deployment
 
 if __name__ == '__main__':
-    deployment = KeystoneBasicDeployment(series='bionic')
+    deployment = KeystoneV3Deployment(series='bionic')
     deployment.run_tests()
diff --git a/tests/dev-basic-xenial-queens b/tests/gate-basic-xenial-queens
similarity index 73%
rename from tests/dev-basic-xenial-queens
rename to tests/gate-basic-xenial-queens
index 22b825b61f08460e63238f34d8d2977252ef9286..1aa6bbc73d57215120cd8736e22ecc78c13bbdd5 100755
--- a/tests/dev-basic-xenial-queens
+++ b/tests/gate-basic-xenial-queens
@@ -16,10 +16,10 @@
 
 """Amulet tests on a basic keystone deployment on xenial-queens."""
 
-from basic_deployment import KeystoneBasicDeployment
+from basic_deployment import KeystoneV3Deployment
 
 if __name__ == '__main__':
-    deployment = KeystoneBasicDeployment(series='xenial',
-                                         openstack='cloud:xenial-queens',
-                                         source='cloud:xenial-updates/queens')
+    deployment = KeystoneV3Deployment(series='xenial',
+                                      openstack='cloud:xenial-queens',
+                                      source='cloud:xenial-updates/queens')
     deployment.run_tests()