From 2dfa16751fe9ff6c270d03f025dd715a0b9300df Mon Sep 17 00:00:00 2001 From: Liam Young Date: Fri, 13 Apr 2018 13:09:57 +0000 Subject: [PATCH] Update vault tests for VIP testing Update vault tests to support an explicit client pointing at the vip. Also, switch to using a namedtuple for managing the clients to allow client metadata to be stored along with the hvac client. --- zaza/charm_tests/vault/setup.py | 8 ++- zaza/charm_tests/vault/tests.py | 36 +++++----- zaza/charm_tests/vault/utils.py | 118 ++++++++++++++++++++++++-------- 3 files changed, 117 insertions(+), 45 deletions(-) diff --git a/zaza/charm_tests/vault/setup.py b/zaza/charm_tests/vault/setup.py index 769c760..f5437d7 100644 --- a/zaza/charm_tests/vault/setup.py +++ b/zaza/charm_tests/vault/setup.py @@ -5,12 +5,16 @@ import zaza.charm_tests.vault.utils as vault_utils def basic_setup(): clients = vault_utils.get_clients() - unseal_client = clients[0] + vip_client = vault_utils.get_vip_client() + if vip_client: + unseal_client = vip_client + else: + unseal_client = clients[0] initialized = vault_utils.is_initialized(unseal_client) # The credentials are written to a file to allow the tests to be re-run # this is mainly useful for manually working on the tests. if initialized: vault_creds = vault_utils.get_credentails() else: - vault_creds = vault_utils.init_vault(unseal_client[1]) + vault_creds = vault_utils.init_vault(unseal_client) vault_utils.store_credentails(vault_creds) diff --git a/zaza/charm_tests/vault/tests.py b/zaza/charm_tests/vault/tests.py index 33fae16..dff2663 100644 --- a/zaza/charm_tests/vault/tests.py +++ b/zaza/charm_tests/vault/tests.py @@ -14,33 +14,36 @@ class VaultTest(unittest.TestCase): @classmethod def setUpClass(cls): cls.clients = vault_utils.get_clients() + cls.vip_client = vault_utils.get_vip_client() + if cls.vip_client: + cls.clients.append(cls.vip_client) vault_creds = vault_utils.get_credentails() vault_utils.unseal_all(cls.clients, vault_creds['keys'][0]) vault_utils.auth_all(cls.clients, vault_creds['root_token']) def test_all_clients_authenticated(self): - for (addr, client) in self.clients: + for client in self.clients: for i in range(1, 10): try: - self.assertTrue(client.is_authenticated()) + self.assertTrue(client.hvac_client.is_authenticated()) except hvac.exceptions.InternalServerError: time.sleep(2) else: break else: - self.assertTrue(client.is_authenticated()) + self.assertTrue(client.hvac_client.is_authenticated()) def check_read(self, key, value): - for (addr, client) in self.clients: + for client in self.clients: self.assertEqual( - client.read('secret/uuids')['data']['uuid'], + client.hvac_client.read('secret/uuids')['data']['uuid'], value) def test_consistent_read_write(self): key = 'secret/uuids' - for (addr, client) in self.clients: + for client in self.clients: value = str(uuid.uuid1()) - client.write(key, uuid=value, lease='1h') + client.hvac_client.write(key, uuid=value, lease='1h') # Now check all clients read the same value back self.check_read(key, value) @@ -49,14 +52,15 @@ class VaultTest(unittest.TestCase): leader = [] leader_address = [] leader_cluster_address = [] - for (addr, client) in self.clients: - self.assertTrue(client.ha_status['ha_enabled']) + for client in self.clients: + self.assertTrue(client.hvac_client.ha_status['ha_enabled']) leader_address.append( - client.ha_status['leader_address']) + client.hvac_client.ha_status['leader_address']) leader_cluster_address.append( - client.ha_status['leader_cluster_address']) - if client.ha_status['is_self']: - leader.append(addr) + client.hvac_client.ha_status['leader_cluster_address']) + if (client.hvac_client.ha_status['is_self'] and not + client.vip_client): + leader.append(client.addr) # Check there is exactly one leader self.assertEqual(len(leader), 1) # Check both cluster addresses match accross the cluster @@ -64,9 +68,9 @@ class VaultTest(unittest.TestCase): self.assertEqual(len(set(leader_cluster_address)), 1) def test_check_vault_status(self): - for (addr, client) in self.clients: - self.assertFalse(client.seal_status['sealed']) - self.assertTrue(client.seal_status['cluster_name']) + for client in self.clients: + self.assertFalse(client.hvac_client.seal_status['sealed']) + self.assertTrue(client.hvac_client.seal_status['cluster_name']) if __name__ == '__main__': diff --git a/zaza/charm_tests/vault/utils.py b/zaza/charm_tests/vault/utils.py index d7ba099..16ff5de 100644 --- a/zaza/charm_tests/vault/utils.py +++ b/zaza/charm_tests/vault/utils.py @@ -7,58 +7,103 @@ import time import urllib3 import yaml +import collections + import zaza.charm_lifecycle.utils as utils import zaza.model AUTH_FILE = "vault_tests.yaml" +CharmVaultClient = collections.namedtuple( + 'CharmVaultClient', ['addr', 'hvac_client', 'vip_client']) -def get_client(vault_url): +def get_unit_api_url(ip): + """Return URL for api access + + :param unit_ip: IP address to use in vault url + :type unit_ip: str + :returns: URL + :rtype: atr + """ + return 'http://{}:8200'.format(ip) + + +def get_hvac_client(vault_url): """Return an hvac client for the given URL :param vault_url: Vault url to point client at - :returns: hvac.Client + :type vault_url: str + :returns: hvac client for given url + :rtype: hvac.Client """ return hvac.Client(url=vault_url) +def get_vip_client(): + """Return CharmVaultClient for the vip if a vip is being used + + :returns: CharmVaultClient + :rtype: CharmVaultClient or None + """ + client = None + vault_config = zaza.model.get_application_config( + utils.get_juju_model(), 'vault') + vip = vault_config.get('vip', {}).get('value') + if vip: + client = CharmVaultClient( + vip, + get_hvac_client(get_unit_api_url(vip)), + True) + return client + + def init_vault(client, shares=1, threshold=1): """Initialise vault - :param client: hvac.Client Client to use for initiliasation - :param shares: int Number of key shares to create - :param threshold: int Number of keys needed to unseal vault - :returns: hvac.Client + :param client: Client to use for initiliasation + :type client: CharmVaultClient + :param shares: Number of key shares to create + :type shares: int + :param threshold: Number of keys needed to unseal vault + :type threshold: int + :returns: Token and key(s) for accessing vault + :rtype: dict """ - return client.initialize(shares, threshold) + return client.hvac_client.initialize(shares, threshold) def get_clients(units=None): """Create a list of clients, one per vault server - :param units: [ip1, ip2, ...] List of IP addresses of vault endpoints - :returns: [hvac.Client, ...] List of clients + :param units: List of IP addresses of vault endpoints + :type units: [str, str, ...] + :returns: List of CharmVaultClients + :rtype: [CharmVaultClient, ...] """ if not units: units = zaza.model.get_app_ips(utils.get_juju_model(), 'vault') clients = [] for unit in units: - vault_url = 'http://{}:8200'.format(unit) - clients.append((unit, get_client(vault_url))) + vault_url = get_unit_api_url(unit) + clients.append(CharmVaultClient( + unit, + get_hvac_client(vault_url), + False)) return clients def is_initialized(client): """Check if vault is initialized - :param client: hvac.Client Client to use to check if vault is - initialized - :returns: bool + :param client: Client to use to check if vault is initialized + :type client: CharmVaultClient + :returns: Whether vault is initialized + :rtype: bool """ initialized = False for i in range(1, 10): try: - initialized = client[1].is_initialized() + initialized = client.hvac_client.is_initialized() except (ConnectionRefusedError, urllib3.exceptions.NewConnectionError, urllib3.exceptions.MaxRetryError, @@ -72,6 +117,12 @@ def is_initialized(client): def get_credentails(): + """Retrieve vault token and keys from unit. These are stored on a unit + during functional tests. + + :returns: Tokens and keys for accessing test environment + :rtype: dict + """ unit = zaza.model.get_first_unit_name(utils.get_juju_model(), 'vault') with tempfile.TemporaryDirectory() as tmpdirname: tmp_file = '{}/{}'.format(tmpdirname, AUTH_FILE) @@ -86,6 +137,12 @@ def get_credentails(): def store_credentails(creds): + """Store the supplied credentials on a vault unit. ONLY USE FOR FUNCTIONAL + TESTING. + + :param creds: Keys and token to store + :type creds: dict + """ unit = zaza.model.get_first_unit_name(utils.get_juju_model(), 'vault') with tempfile.NamedTemporaryFile(mode='w') as fp: fp.write(yaml.dump(creds)) @@ -100,8 +157,10 @@ def store_credentails(creds): def get_credentails_from_file(auth_file): """Read the vault credentials from the auth_file - :param auth_file: str Path to file with credentials - :returns: {} Dictionary of credentials + :param auth_file: Path to file with credentials + :type auth_file: str + :returns: Token and keys + :rtype: dict """ with open(auth_file, 'r') as stream: vault_creds = yaml.load(stream) @@ -111,7 +170,8 @@ def get_credentails_from_file(auth_file): def write_credentails(auth_file, vault_creds): """Write the vault credentials to the auth_file - :param auth_file: str Path to file to write credentials + :param auth_file: Path to file to write credentials + :type auth_file: str """ with open(auth_file, 'w') as outfile: yaml.dump(vault_creds, outfile, default_flow_style=False) @@ -120,19 +180,23 @@ def write_credentails(auth_file, vault_creds): def unseal_all(clients, key): """Unseal all the vaults with the given clients with the provided key - :param clients: [hvac.Client, ...] List of clients - :param key: str key to unlock clients + :param clients: List of clients + :type clients: [CharmVaultClient, ...] + :param key: key to unlock clients + :type key: str """ - for (addr, client) in clients: - if client.is_sealed(): - client.unseal(key) + for client in clients: + if client.hvac_client.is_sealed(): + client.hvac_client.unseal(key) def auth_all(clients, token): """Authenticate all the given clients with the provided token - :param clients: [hvac.Client, ...] List of clients - :param token: str token to authorize clients + :param clients: List of clients + :type clients: [CharmVaultClient, ...] + :param token: Token to authorize clients + :type token: str """ - for (addr, client) in clients: - client.token = token + for client in clients: + client.hvac_client.token = token