146 lines
5.4 KiB
Python
146 lines
5.4 KiB
Python
# Copyright 2023 Canonical Ltd.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
"""Keystone LDAP tests on k8s."""
|
|
|
|
import json
|
|
import tenacity
|
|
import contextlib
|
|
from keystoneauth1.exceptions.connection import ConnectFailure
|
|
from keystoneauth1.exceptions.http import NotFound as http_NotFound
|
|
import logging
|
|
from requests.exceptions import ConnectionError
|
|
import zaza.openstack.charm_tests.keystone.tests as ks_tests
|
|
import zaza.openstack.charm_tests.tempest.tests as tempest_tests
|
|
import zaza.charm_lifecycle.utils as lifecycle_utils
|
|
import zaza.model
|
|
import subprocess
|
|
|
|
|
|
class KeystoneLookupError(Exception):
|
|
"""An error looking up data in keystone."""
|
|
|
|
pass
|
|
|
|
|
|
class LdapExplicitCharmConfigTestsK8S(ks_tests.LdapExplicitCharmConfigTests):
|
|
"""Keystone LDAP tests for K8s deployment."""
|
|
|
|
@classmethod
|
|
def setUpClass(cls):
|
|
"""Run class setup for running Keystone ldap-tests."""
|
|
cls.model_name = zaza.model.get_juju_model()
|
|
cls.test_config = lifecycle_utils.get_charm_config(fatal=False)
|
|
cls.default_api_version = 3
|
|
cls.api_v3 = 3
|
|
cls.keystone_ips = cls.get_internal_ips("keystone", cls.model_name)
|
|
|
|
@contextlib.contextmanager
|
|
def v3_keystone_preferred(self):
|
|
"""Set the preferred keystone api to v3 within called context."""
|
|
with contextlib.nullcontext():
|
|
yield
|
|
|
|
@staticmethod
|
|
def get_internal_ips(application, model_name):
|
|
"""Return the internal ip addresses an application."""
|
|
status = zaza.model.get_status(model_name=model_name)
|
|
units = status['applications'][application]["units"]
|
|
return [v.address for v in units.values()]
|
|
|
|
def get_ldap_ips(self):
|
|
"""Return the ip addresses for the ldap servers."""
|
|
return self.get_internal_ips("ldap-server", self.model_name)
|
|
|
|
def get_domain_config(self):
|
|
"""Collect the rendered domain config file."""
|
|
# libjuju does not support ssh to a payload container
|
|
cmd = [
|
|
"juju",
|
|
"ssh",
|
|
"-m",
|
|
self.model_name,
|
|
"--container",
|
|
"keystone",
|
|
zaza.model.get_lead_unit("keystone").entity_id,
|
|
'cat /etc/keystone/domains/keystone.userdomain.conf']
|
|
out = subprocess.check_output(cmd)
|
|
return out.decode()
|
|
|
|
def _get_ldap_config(self):
|
|
"""Generate ldap config for current model.
|
|
|
|
:return: tuple of whether ldap-server is running and if so, config
|
|
for the keystone-ldap application.
|
|
:rtype: Tuple[bool, Dict[str,str]]
|
|
"""
|
|
ldap_ips = self.get_ldap_ips()
|
|
self.assertTrue(ldap_ips, "Should be at least one ldap server")
|
|
config_flags = json.dumps({
|
|
'url': "ldap://{}".format(ldap_ips[0]),
|
|
'user': 'cn=admin,dc=test,dc=com',
|
|
"use_pool": True,
|
|
'password': 'crapper',
|
|
'suffix': 'dc=test,dc=com',
|
|
'query_scope': 'one',
|
|
'user_objectclass': 'inetOrgPerson',
|
|
'user_id_attribute': 'cn',
|
|
'user_name_attribute': 'sn',
|
|
'user_enabled_attribute': 'enabled',
|
|
'user_enabled_invert': False,
|
|
'user_enabled_mask': 0,
|
|
'user_enabled_default': 'True',
|
|
'group_tree_dn': 'ou=groups,dc=test,dc=com',
|
|
'group_id_attribute': 'cn',
|
|
'group_name_attribute': 'cn',
|
|
'group_member_attribute': 'memberUid',
|
|
'group_members_are_ids': True,
|
|
"group_objectclass": "posixGroup",
|
|
})
|
|
return {
|
|
"ldap-config-flags": config_flags,
|
|
"domain-name": "userdomain"}
|
|
|
|
@tenacity.retry(wait=tenacity.wait_exponential(multiplier=2, max=60),
|
|
reraise=True, stop=tenacity.stop_after_attempt(5),
|
|
retry=tenacity.retry_if_exception_type(
|
|
KeystoneLookupError))
|
|
def _find_keystone_v3_group(self, group, domain):
|
|
logging.info('Looking for group: {}'.format(group))
|
|
try:
|
|
return super()._find_keystone_v3_group(group, domain)
|
|
except (AttributeError, http_NotFound, ConnectionError,
|
|
ConnectFailure):
|
|
raise KeystoneLookupError
|
|
|
|
@tenacity.retry(wait=tenacity.wait_exponential(multiplier=2, max=60),
|
|
reraise=True, stop=tenacity.stop_after_attempt(5),
|
|
retry=tenacity.retry_if_exception_type(
|
|
KeystoneLookupError))
|
|
def _find_keystone_v3_user(self, username, domain, group=None):
|
|
logging.info('Looking for user: {}'.format(username))
|
|
try:
|
|
return super()._find_keystone_v3_user(
|
|
username,
|
|
domain,
|
|
group=group)
|
|
except (AttributeError, http_NotFound, ConnectionError,
|
|
ConnectFailure):
|
|
raise KeystoneLookupError
|
|
|
|
|
|
class KeystoneTempestTestK8S(tempest_tests.TempestTestScaleK8SBase):
|
|
"""Test keystone k8s scale out and scale back."""
|
|
|
|
application_name = "keystone"
|