From 28954c27ce58bebadc54d4fd1ca81126c775792c Mon Sep 17 00:00:00 2001 From: Guillaume Belanger Date: Mon, 21 Aug 2023 16:34:41 -0400 Subject: [PATCH] [Feat] Uses clusterIP instead of LoadBalancer service (#31) --- .github/workflows/main.yml | 5 ++- README.md | 7 ++--- src/charm.py | 3 +- tests/integration/kubernetes.py | 43 ------------------------- tests/integration/test_integration.py | 45 ++++++++++----------------- tests/unit/test_charm.py | 2 +- tox.ini | 2 +- 7 files changed, 24 insertions(+), 83 deletions(-) delete mode 100644 tests/integration/kubernetes.py diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3035401d..513d1816 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -44,9 +44,8 @@ jobs: uses: charmed-kubernetes/actions-operator@main with: provider: microk8s - - name: Enable LoadBalancer - run: /usr/bin/sg microk8s -c "microk8s enable metallb:10.0.1.1-10.0.1.3" - + channel: 1.27-strict/stable + juju-channel: 3.1/stable - name: Run integration tests run: tox -e integration - name: Archive charmcraft logs diff --git a/README.md b/README.md index 6916f123..559d750e 100644 --- a/README.md +++ b/README.md @@ -51,11 +51,10 @@ sudo snap install vault ### Initialise Vault Identify the vault unit by setting the ``VAULT_ADDR`` environment variable -based on the IP address of the unit. This can be discovered from `kubectl get services` -output (column 'EXTERNAL-IP'). Here we'll use '10.0.0.126': +based on the IP address of the unit. ```bash -export VAULT_ADDR="http://10.0.0.126:8200" +export VAULT_ADDR="http://10.1.182.39:8200" ``` Initialise Vault by specifying the number of unseal keys that should get @@ -161,7 +160,7 @@ Vault initialization and unsealing can be done using Vault's Python API client: import hvac # Setup -vault = hvac.Client(url="http://10.0.0.126:8200") +vault = hvac.Client(url="http://10.1.182.39:8200") # Initialise initialize_response = vault.sys.initialize(secret_shares=1, secret_threshold=1) diff --git a/src/charm.py b/src/charm.py index 87bba314..5a9171bd 100755 --- a/src/charm.py +++ b/src/charm.py @@ -59,8 +59,7 @@ def __init__(self, *args): self.framework.observe(self.on.authorise_charm_action, self._on_authorise_charm_action) self.service_patcher = KubernetesServicePatch( charm=self, - ports=[ServicePort(name="vault", port=8200)], - service_type="LoadBalancer", + ports=[ServicePort(name="vault", port=self.VAULT_PORT)], ) def _on_certificate_creation_request(self, event: CertificateCreationRequestEvent) -> None: diff --git a/tests/integration/kubernetes.py b/tests/integration/kubernetes.py deleted file mode 100644 index a46d7e2d..00000000 --- a/tests/integration/kubernetes.py +++ /dev/null @@ -1,43 +0,0 @@ -#!/usr/bin/env python3 -# Copyright 2021 Canonical Ltd. -# See LICENSE file for licensing details. - -import logging -from typing import Optional - -from lightkube import Client -from lightkube.resources.core_v1 import Service - -logger = logging.getLogger(__name__) - - -class Kubernetes: - """Class to interact with Vault through its API.""" - - def __init__(self, namespace: str): - self.client = Client() - self.namespace = namespace - - def get_load_balancer_address(self, service_name: str) -> Optional[str]: - """Returns the services' load balancer IP address or hostname. - - Args: - service_name: Kubernetes service Name - - Returns: - str: Load balancer IP address or hostname. - """ - service = self.client.get(Service, service_name, namespace=self.namespace) - ingresses = service.status.loadBalancer.ingress # type: ignore[attr-defined] - if ingresses: - ip = ingresses[0].ip - hostname = ingresses[0].hostname - if hostname: - logger.info(f"Found Loadbalancer address: {hostname}") - return hostname - else: - logger.info(f"Found Loadbalancer address: {ip}") - return ip - else: - logger.info(f"Ingress not yet available for {service_name}") - return None diff --git a/tests/integration/test_integration.py b/tests/integration/test_integration.py index 65ce2e05..389d32d2 100644 --- a/tests/integration/test_integration.py +++ b/tests/integration/test_integration.py @@ -12,7 +12,6 @@ import yaml from pytest_operator.plugin import OpsTest -from tests.integration.kubernetes import Kubernetes from tests.integration.vault import Vault logger = logging.getLogger(__name__) @@ -21,32 +20,22 @@ APPLICATION_NAME = "vault-k8s" -class TestVaultK8s: - @staticmethod - async def wait_for_load_balancer_address( - kubernetes: Kubernetes, timeout: int = 60 - ) -> Optional[str]: - """Waits for LoadBalancer address to be available and returns it. +async def get_unit_address(ops_test: OpsTest, app_name: str, unit_num: int) -> str: + """Get unit's IP address for any application. - Args: - kubernetes: Kubernetes object. - timeout: Timeout (seconds). + Args: + ops_test: OpsTest + app_name: string name of application + unit_num: integer number of a juju unit - Returns: - str: LoadBalancer address. + Returns: + str: Unit's IP address + """ + status = await ops_test.model.get_status() # type: ignore[union-attr] + return status["applications"][app_name]["units"][f"{app_name}/{unit_num}"]["address"] - Raises: - TimeoutError: If LoadBalancer address is not available after timeout. - """ - initial_time = time.time() - while time.time() - initial_time < timeout: - if load_balancer_address := kubernetes.get_load_balancer_address( - service_name=APPLICATION_NAME - ): - return load_balancer_address - time.sleep(5) - raise TimeoutError("Timed out waiting for Loadbalancer address to be available.") +class TestVaultK8s: @staticmethod async def initialize_vault(vault: Vault, timeout: int = 60) -> Optional[Tuple[str, str]]: """Initializes Vault. @@ -105,8 +94,7 @@ async def build_and_deploy(self, ops_test: OpsTest): async def post_deployment_tasks(self, ops_test: OpsTest) -> str: """Runs post deployment tasks as explained in the README.md. - Retrieves Vault's LoadBalancer address, initializes Vault and generates a token for - the charm. + Retrieves Vault's unit address, initializes Vault and generates a token for the charm. Args: ops_test: Ops test Framework. @@ -114,9 +102,8 @@ async def post_deployment_tasks(self, ops_test: OpsTest) -> str: Returns: str: Generated token. """ - kubernetes = Kubernetes(namespace=ops_test.model_name) # type: ignore[arg-type] - load_balancer_address = await self.wait_for_load_balancer_address(kubernetes=kubernetes) - vault = Vault(url=f"http://{load_balancer_address}:8200") + unit_address = await get_unit_address(ops_test, app_name=APPLICATION_NAME, unit_num=0) + vault = Vault(url=f"http://{unit_address}:8200") unseal_key, root_token = await self.initialize_vault(vault=vault) # type: ignore[misc] vault.set_token(root_token) vault.unseal(unseal_key=unseal_key) @@ -124,7 +111,7 @@ async def post_deployment_tasks(self, ops_test: OpsTest) -> str: return generated_token @pytest.mark.abort_on_fail - async def test_given_no_config_when_deploy_then_status_is_blocked( # noqa: E501 + async def test_given_no_config_when_deploy_then_status_is_blocked( self, ops_test: OpsTest, build_and_deploy ): await ops_test.model.wait_for_idle( # type: ignore[union-attr] diff --git a/tests/unit/test_charm.py b/tests/unit/test_charm.py index dbd20a7c..8b96d054 100644 --- a/tests/unit/test_charm.py +++ b/tests/unit/test_charm.py @@ -16,7 +16,7 @@ class TestCharm(unittest.TestCase): @patch( "charm.KubernetesServicePatch", - lambda charm, ports, service_type: None, + lambda charm, ports: None, ) def setUp(self): self.harness = testing.Harness(VaultCharm) diff --git a/tox.ini b/tox.ini index f5b08697..a9c13278 100644 --- a/tox.ini +++ b/tox.ini @@ -76,7 +76,7 @@ commands = [testenv:integration] description = Run integration tests deps = - juju<3.1 + juju pytest pytest-operator -r{toxinidir}/requirements.txt