Merge pull request #156 from addyess/bug/1828424

Bug/1828424
This commit is contained in:
David Ames
2020-02-03 13:23:33 -08:00
committed by GitHub
3 changed files with 148 additions and 27 deletions

View File

@@ -13,6 +13,7 @@
# limitations under the License.
"""Collection of code for setting up and testing keystone."""
import contextlib
import zaza
import zaza.openstack.charm_tests.test_utils as test_utils
import zaza.openstack.utilities.openstack as openstack_utils
@@ -59,3 +60,12 @@ class BaseKeystoneTest(test_utils.OpenStackBaseTest):
openstack_utils.get_keystone_session_client(
cls.admin_keystone_session,
client_api_version=cls.default_api_version))
@contextlib.contextmanager
def v3_keystone_preferred(self):
"""Set the preferred keystone api to v3 within called context."""
with self.config_change(
{'preferred-api-version': self.default_api_version},
{'preferred-api-version': '3'},
application_name="keystone"):
yield

View File

@@ -17,14 +17,13 @@ import collections
import json
import logging
import pprint
import keystoneauth1
import zaza.model
import zaza.openstack.utilities.exceptions as zaza_exceptions
import zaza.openstack.utilities.juju as juju_utils
import zaza.openstack.utilities.openstack as openstack_utils
import zaza.charm_lifecycle.utils as lifecycle_utils
import zaza.openstack.charm_tests.test_utils as test_utils
from zaza.openstack.charm_tests.keystone import (
BaseKeystoneTest,
@@ -189,10 +188,7 @@ class AuthenticationAuthorizationTest(BaseKeystoneTest):
openstack_utils.get_os_release('trusty_mitaka')):
logging.info('skipping test < trusty_mitaka')
return
with self.config_change(
{'preferred-api-version': self.default_api_version},
{'preferred-api-version': '3'},
application_name="keystone"):
with self.v3_keystone_preferred():
for ip in self.keystone_ips:
try:
logging.info('keystone IP {}'.format(ip))
@@ -212,7 +208,7 @@ class AuthenticationAuthorizationTest(BaseKeystoneTest):
def test_end_user_domain_admin_access(self):
"""Verify that end-user domain admin does not have elevated privileges.
In additon to validating that the `policy.json` is written and the
In addition to validating that the `policy.json` is written and the
service is restarted on config-changed, the test validates that our
`policy.json` is correct.
@@ -222,10 +218,7 @@ class AuthenticationAuthorizationTest(BaseKeystoneTest):
openstack_utils.get_os_release('xenial_ocata')):
logging.info('skipping test < xenial_ocata')
return
with self.config_change(
{'preferred-api-version': self.default_api_version},
{'preferred-api-version': '3'},
application_name="keystone"):
with self.v3_keystone_preferred():
for ip in self.keystone_ips:
openrc = {
'API_VERSION': 3,
@@ -257,7 +250,7 @@ class AuthenticationAuthorizationTest(BaseKeystoneTest):
'allowed when it should not be.')
logging.info('OK')
def test_end_user_acccess_and_token(self):
def test_end_user_access_and_token(self):
"""Verify regular end-user access resources and validate token data.
In effect this also validates user creation, presence of standard
@@ -371,3 +364,81 @@ class SecurityTests(BaseKeystoneTest):
expected_passes,
expected_failures,
expected_to_pass=False)
class LdapTests(BaseKeystoneTest):
"""Keystone ldap tests tests."""
@classmethod
def setUpClass(cls):
"""Run class setup for running Keystone ldap-tests."""
super(LdapTests, cls).setUpClass()
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 = zaza.model.get_app_ips("ldap-server")
self.assertTrue(ldap_ips, "Should be at least one ldap server")
return {
'ldap-server': "ldap://{}".format(ldap_ips[0]),
'ldap-user': 'cn=admin,dc=test,dc=com',
'ldap-password': 'crapper',
'ldap-suffix': 'dc=test,dc=com',
'domain-name': 'userdomain',
}
def _find_keystone_v3_user(self, username, domain):
"""Find a user within a specified keystone v3 domain.
:param str username: Username to search for in keystone
:param str domain: username selected from which domain
:return: return username if found
:rtype: Optional[str]
"""
for ip in self.keystone_ips:
logging.info('Keystone IP {}'.format(ip))
session = openstack_utils.get_keystone_session(
openstack_utils.get_overcloud_auth(address=ip))
client = openstack_utils.get_keystone_session_client(session)
domain_users = client.users.list(
domain=client.domains.find(name=domain).id
)
usernames = [u.name.lower() for u in domain_users]
if username.lower() in usernames:
return username
logging.debug(
"User {} was not found. Returning None.".format(username)
)
return None
def test_100_keystone_ldap_users(self):
"""Validate basic functionality of keystone API with ldap."""
application_name = 'keystone-ldap'
config = self._get_ldap_config()
with self.config_change(
self.config_current(application_name, config.keys()),
config,
application_name=application_name):
logging.info(
'Waiting for users to become available in keystone...'
)
test_config = lifecycle_utils.get_charm_config(fatal=False)
zaza.model.wait_for_application_states(
states=test_config.get("target_deploy_status", {})
)
with self.v3_keystone_preferred():
# NOTE(jamespage): Test fixture should have johndoe and janedoe
# accounts
johndoe = self._find_keystone_v3_user('john doe', 'userdomain')
self.assertIsNotNone(johndoe, "user 'john doe' was unknown")
janedoe = self._find_keystone_v3_user('jane doe', 'userdomain')
self.assertIsNotNone(janedoe, "user 'jane doe' was unknown")

View File

@@ -11,12 +11,11 @@
# 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.
"""Module containg base class for implementing charm tests."""
"""Module containing base class for implementing charm tests."""
import contextlib
import logging
import subprocess
import unittest
import zaza.model
import zaza.model as model
import zaza.charm_lifecycle.utils as lifecycle_utils
@@ -28,7 +27,7 @@ def skipIfNotHA(service_name):
"""Run decorator to skip tests if application not in HA configuration."""
def _skipIfNotHA_inner_1(f):
def _skipIfNotHA_inner_2(*args, **kwargs):
ips = zaza.model.get_app_ips(
ips = model.get_app_ips(
service_name)
if len(ips) > 1:
return f(*args, **kwargs)
@@ -70,7 +69,7 @@ def audit_assertions(action,
:param expected_passes: List of test names that are expected to pass
:type expected_passes: List[str]
:param expected_failures: List of test names that are expected to fail
:type expexted_failures: List[str]
:type expected_failures: List[str]
:raises: AssertionError if the assertion fails.
"""
if expected_failures is None:
@@ -115,7 +114,7 @@ class OpenStackBaseTest(unittest.TestCase):
@classmethod
def setUpClass(cls, application_name=None, model_alias=None):
"""Run setup for test class to create common resourcea."""
"""Run setup for test class to create common resources."""
cls.model_aliases = model.get_juju_model_aliases()
if model_alias:
cls.model_name = cls.model_aliases[model_alias]
@@ -133,6 +132,50 @@ class OpenStackBaseTest(unittest.TestCase):
model_name=cls.model_name)
logging.debug('Leader unit is {}'.format(cls.lead_unit))
def config_current(self, application_name=None, keys=None):
"""Get Current Config of an application normalized into key-values.
:param application_name: String application name for use when called
by a charm under test other than the object's
application.
:type application_name: Optional[str]
:param keys: iterable of strs to index into the current config. If
None, return all keys from the config
:type keys: Optional[Iterable[str]]
:return: Dictionary of requested config from application
:rtype: Dict[str, Any]
"""
if not application_name:
application_name = self.application_name
_app_config = model.get_application_config(application_name)
keys = keys or _app_config.keys()
return {
k: _app_config.get(k, {}).get('value')
for k in keys
}
@staticmethod
def _stringed_value_config(config):
"""Stringify values in a dict.
Workaround:
libjuju refuses to accept data with types other than strings
through the zuzu.model.set_application_config
:param config: Config dictionary with any typed values
:type config: Dict[str,Any]
:return: Config Dictionary with string-ly typed values
:rtype: Dict[str,str]
"""
# if v is None, stringify to ''
# otherwise use a strict cast with str(...)
return {
k: '' if v is None else str(v)
for k, v in config.items()
}
@contextlib.contextmanager
def config_change(self, default_config, alternate_config,
application_name=None):
@@ -158,17 +201,14 @@ class OpenStackBaseTest(unittest.TestCase):
"""
if not application_name:
application_name = self.application_name
# we need to compare config values to what is already applied before
# attempting to set them. otherwise the model will behave differently
# than we would expect while waiting for completion of the change
_app_config = model.get_application_config(application_name)
app_config = {}
# convert the more elaborate config structure from libjuju to something
# we can compare to what the caller supplies to this function
for k in alternate_config.keys():
# note that conversion to string for all values is due to
# attempting to set any config with other types lead to Traceback
app_config[k] = str(_app_config.get(k, {}).get('value', ''))
app_config = self.config_current(
application_name, keys=alternate_config.keys()
)
if all(item in app_config.items()
for item in alternate_config.items()):
logging.debug('alternate_config equals what is already applied '
@@ -185,7 +225,7 @@ class OpenStackBaseTest(unittest.TestCase):
.format(alternate_config))
model.set_application_config(
application_name,
alternate_config,
self._stringed_value_config(alternate_config),
model_name=self.model_name)
logging.debug(
@@ -205,7 +245,7 @@ class OpenStackBaseTest(unittest.TestCase):
logging.debug('Restoring charm setting to {}'.format(default_config))
model.set_application_config(
application_name,
default_config,
self._stringed_value_config(default_config),
model_name=self.model_name)
logging.debug(