@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user