diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml
index a87fb15..eac6dc2 100644
--- a/.github/workflows/main.yaml
+++ b/.github/workflows/main.yaml
@@ -27,7 +27,7 @@ jobs:
integration-test:
uses: canonical/sdcore-github-workflows/.github/workflows/integration-test.yaml@main
with:
- charm-file-name: "sdcore-pcf_ubuntu-22.04-amd64.charm"
+ charm-file-name: "sdcore-pcf-k8s_ubuntu-22.04-amd64.charm"
publish-charm:
name: Publish Charm
@@ -39,5 +39,5 @@ jobs:
if: ${{ github.ref_name == 'main' }}
uses: canonical/sdcore-github-workflows/.github/workflows/publish-charm.yaml@main
with:
- charm-file-name: "sdcore-pcf_ubuntu-22.04-amd64.charm"
+ charm-file-name: "sdcore-pcf-k8s_ubuntu-22.04-amd64.charm"
secrets: inherit
diff --git a/README.md b/README.md
index 4ef4ec3..00be913 100644
--- a/README.md
+++ b/README.md
@@ -1,33 +1,24 @@
-
-
![ONF Icon](./icon.svg)
-
-
-
-
![CharmHub Badge](https://charmhub.io/sdcore-pcf/badge.svg)
-
-
-
-
-
-
SD-Core PCF Operator
-
-
-A Charmed Operator for SD-Core's Policy Control Function (PCF) component.
+# SD-Core PCF Operator (k8s)
+[![CharmHub Badge](https://charmhub.io/sdcore-pcf-k8s/badge.svg)](https://charmhub.io/sdcore-pcf-k8s)
+
+A Charmed Operator for SD-Core's Policy Control Function (PCF) component for K8s.
## Usage
```bash
juju deploy mongodb-k8s --channel 6/beta --trust
-juju deploy sdcore-nrf --channel edge --trust
-juju deploy sdcore-pcf --channel edge --trust
+juju deploy sdcore-nrf-k8s --channel edge
+juju deploy sdcore-pcf-k8s --channel edge
+
juju deploy self-signed-certificates --channel=beta
-juju integrate sdcore-pcf mongodb-k8s
-juju integrate sdcore-nrf self-signed-certificates
-juju integrate sdcore-pcf:fiveg_nrf sdcore-nrf
-juju integrate sdcore-pcf:certificates self-signed-certificates:certificates
+juju integrate sdcore-pcf-k8s mongodb-k8s
+juju integrate sdcore-nrf-k8s self-signed-certificates
+juju integrate sdcore-pcf-k8s:fiveg_nrf sdcore-nrf-k8s:fiveg_nrf
+juju integrate sdcore-pcf-k8s:certificates self-signed-certificates:certificates
```
## Image
**pcf**: `ghcr.io/canonical/sdcore-pcf:1.3`
+
diff --git a/lib/charms/observability_libs/v1/kubernetes_service_patch.py b/lib/charms/observability_libs/v1/kubernetes_service_patch.py
deleted file mode 100644
index 64dd13c..0000000
--- a/lib/charms/observability_libs/v1/kubernetes_service_patch.py
+++ /dev/null
@@ -1,341 +0,0 @@
-# Copyright 2021 Canonical Ltd.
-# See LICENSE file for licensing details.
-
-"""# KubernetesServicePatch Library.
-
-This library is designed to enable developers to more simply patch the Kubernetes Service created
-by Juju during the deployment of a sidecar charm. When sidecar charms are deployed, Juju creates a
-service named after the application in the namespace (named after the Juju model). This service by
-default contains a "placeholder" port, which is 65536/TCP.
-
-When modifying the default set of resources managed by Juju, one must consider the lifecycle of the
-charm. In this case, any modifications to the default service (created during deployment), will be
-overwritten during a charm upgrade.
-
-When initialised, this library binds a handler to the parent charm's `install` and `upgrade_charm`
-events which applies the patch to the cluster. This should ensure that the service ports are
-correct throughout the charm's life.
-
-The constructor simply takes a reference to the parent charm, and a list of
-[`lightkube`](https://github.com/gtsystem/lightkube) ServicePorts that each define a port for the
-service. For information regarding the `lightkube` `ServicePort` model, please visit the
-`lightkube` [docs](https://gtsystem.github.io/lightkube-models/1.23/models/core_v1/#serviceport).
-
-Optionally, a name of the service (in case service name needs to be patched as well), labels,
-selectors, and annotations can be provided as keyword arguments.
-
-## Getting Started
-
-To get started using the library, you just need to fetch the library using `charmcraft`. **Note
-that you also need to add `lightkube` and `lightkube-models` to your charm's `requirements.txt`.**
-
-```shell
-cd some-charm
-charmcraft fetch-lib charms.observability_libs.v1.kubernetes_service_patch
-cat << EOF >> requirements.txt
-lightkube
-lightkube-models
-EOF
-```
-
-Then, to initialise the library:
-
-For `ClusterIP` services:
-
-```python
-# ...
-from charms.observability_libs.v1.kubernetes_service_patch import KubernetesServicePatch
-from lightkube.models.core_v1 import ServicePort
-
-class SomeCharm(CharmBase):
- def __init__(self, *args):
- # ...
- port = ServicePort(443, name=f"{self.app.name}")
- self.service_patcher = KubernetesServicePatch(self, [port])
- # ...
-```
-
-For `LoadBalancer`/`NodePort` services:
-
-```python
-# ...
-from charms.observability_libs.v1.kubernetes_service_patch import KubernetesServicePatch
-from lightkube.models.core_v1 import ServicePort
-
-class SomeCharm(CharmBase):
- def __init__(self, *args):
- # ...
- port = ServicePort(443, name=f"{self.app.name}", targetPort=443, nodePort=30666)
- self.service_patcher = KubernetesServicePatch(
- self, [port], "LoadBalancer"
- )
- # ...
-```
-
-Port protocols can also be specified. Valid protocols are `"TCP"`, `"UDP"`, and `"SCTP"`
-
-```python
-# ...
-from charms.observability_libs.v1.kubernetes_service_patch import KubernetesServicePatch
-from lightkube.models.core_v1 import ServicePort
-
-class SomeCharm(CharmBase):
- def __init__(self, *args):
- # ...
- tcp = ServicePort(443, name=f"{self.app.name}-tcp", protocol="TCP")
- udp = ServicePort(443, name=f"{self.app.name}-udp", protocol="UDP")
- sctp = ServicePort(443, name=f"{self.app.name}-sctp", protocol="SCTP")
- self.service_patcher = KubernetesServicePatch(self, [tcp, udp, sctp])
- # ...
-```
-
-Bound with custom events by providing `refresh_event` argument:
-For example, you would like to have a configurable port in your charm and want to apply
-service patch every time charm config is changed.
-
-```python
-from charms.observability_libs.v1.kubernetes_service_patch import KubernetesServicePatch
-from lightkube.models.core_v1 import ServicePort
-
-class SomeCharm(CharmBase):
- def __init__(self, *args):
- # ...
- port = ServicePort(int(self.config["charm-config-port"]), name=f"{self.app.name}")
- self.service_patcher = KubernetesServicePatch(
- self,
- [port],
- refresh_event=self.on.config_changed
- )
- # ...
-```
-
-Additionally, you may wish to use mocks in your charm's unit testing to ensure that the library
-does not try to make any API calls, or open any files during testing that are unlikely to be
-present, and could break your tests. The easiest way to do this is during your test `setUp`:
-
-```python
-# ...
-
-@patch("charm.KubernetesServicePatch", lambda x, y: None)
-def setUp(self, *unused):
- self.harness = Harness(SomeCharm)
- # ...
-```
-"""
-
-import logging
-from types import MethodType
-from typing import List, Literal, Optional, Union
-
-from lightkube import ApiError, Client
-from lightkube.core import exceptions
-from lightkube.models.core_v1 import ServicePort, ServiceSpec
-from lightkube.models.meta_v1 import ObjectMeta
-from lightkube.resources.core_v1 import Service
-from lightkube.types import PatchType
-from ops.charm import CharmBase
-from ops.framework import BoundEvent, Object
-
-logger = logging.getLogger(__name__)
-
-# The unique Charmhub library identifier, never change it
-LIBID = "0042f86d0a874435adef581806cddbbb"
-
-# Increment this major API version when introducing breaking changes
-LIBAPI = 1
-
-# Increment this PATCH version before using `charmcraft publish-lib` or reset
-# to 0 if you are raising the major API version
-LIBPATCH = 7
-
-ServiceType = Literal["ClusterIP", "LoadBalancer"]
-
-
-class KubernetesServicePatch(Object):
- """A utility for patching the Kubernetes service set up by Juju."""
-
- def __init__(
- self,
- charm: CharmBase,
- ports: List[ServicePort],
- service_name: Optional[str] = None,
- service_type: ServiceType = "ClusterIP",
- additional_labels: Optional[dict] = None,
- additional_selectors: Optional[dict] = None,
- additional_annotations: Optional[dict] = None,
- *,
- refresh_event: Optional[Union[BoundEvent, List[BoundEvent]]] = None,
- ):
- """Constructor for KubernetesServicePatch.
-
- Args:
- charm: the charm that is instantiating the library.
- ports: a list of ServicePorts
- service_name: allows setting custom name to the patched service. If none given,
- application name will be used.
- service_type: desired type of K8s service. Default value is in line with ServiceSpec's
- default value.
- additional_labels: Labels to be added to the kubernetes service (by default only
- "app.kubernetes.io/name" is set to the service name)
- additional_selectors: Selectors to be added to the kubernetes service (by default only
- "app.kubernetes.io/name" is set to the service name)
- additional_annotations: Annotations to be added to the kubernetes service.
- refresh_event: an optional bound event or list of bound events which
- will be observed to re-apply the patch (e.g. on port change).
- The `install` and `upgrade-charm` events would be observed regardless.
- """
- super().__init__(charm, "kubernetes-service-patch")
- self.charm = charm
- self.service_name = service_name if service_name else self._app
- self.service = self._service_object(
- ports,
- service_name,
- service_type,
- additional_labels,
- additional_selectors,
- additional_annotations,
- )
-
- # Make mypy type checking happy that self._patch is a method
- assert isinstance(self._patch, MethodType)
- # Ensure this patch is applied during the 'install' and 'upgrade-charm' events
- self.framework.observe(charm.on.install, self._patch)
- self.framework.observe(charm.on.upgrade_charm, self._patch)
- self.framework.observe(charm.on.update_status, self._patch)
-
- # apply user defined events
- if refresh_event:
- if not isinstance(refresh_event, list):
- refresh_event = [refresh_event]
-
- for evt in refresh_event:
- self.framework.observe(evt, self._patch)
-
- def _service_object(
- self,
- ports: List[ServicePort],
- service_name: Optional[str] = None,
- service_type: ServiceType = "ClusterIP",
- additional_labels: Optional[dict] = None,
- additional_selectors: Optional[dict] = None,
- additional_annotations: Optional[dict] = None,
- ) -> Service:
- """Creates a valid Service representation.
-
- Args:
- ports: a list of ServicePorts
- service_name: allows setting custom name to the patched service. If none given,
- application name will be used.
- service_type: desired type of K8s service. Default value is in line with ServiceSpec's
- default value.
- additional_labels: Labels to be added to the kubernetes service (by default only
- "app.kubernetes.io/name" is set to the service name)
- additional_selectors: Selectors to be added to the kubernetes service (by default only
- "app.kubernetes.io/name" is set to the service name)
- additional_annotations: Annotations to be added to the kubernetes service.
-
- Returns:
- Service: A valid representation of a Kubernetes Service with the correct ports.
- """
- if not service_name:
- service_name = self._app
- labels = {"app.kubernetes.io/name": self._app}
- if additional_labels:
- labels.update(additional_labels)
- selector = {"app.kubernetes.io/name": self._app}
- if additional_selectors:
- selector.update(additional_selectors)
- return Service(
- apiVersion="v1",
- kind="Service",
- metadata=ObjectMeta(
- namespace=self._namespace,
- name=service_name,
- labels=labels,
- annotations=additional_annotations, # type: ignore[arg-type]
- ),
- spec=ServiceSpec(
- selector=selector,
- ports=ports,
- type=service_type,
- ),
- )
-
- def _patch(self, _) -> None:
- """Patch the Kubernetes service created by Juju to map the correct port.
-
- Raises:
- PatchFailed: if patching fails due to lack of permissions, or otherwise.
- """
- try:
- client = Client()
- except exceptions.ConfigError as e:
- logger.warning("Error creating k8s client: %s", e)
- return
-
- try:
- if self._is_patched(client):
- return
- if self.service_name != self._app:
- self._delete_and_create_service(client)
- client.patch(Service, self.service_name, self.service, patch_type=PatchType.MERGE)
- except ApiError as e:
- if e.status.code == 403:
- logger.error("Kubernetes service patch failed: `juju trust` this application.")
- else:
- logger.error("Kubernetes service patch failed: %s", str(e))
- else:
- logger.info("Kubernetes service '%s' patched successfully", self._app)
-
- def _delete_and_create_service(self, client: Client):
- service = client.get(Service, self._app, namespace=self._namespace)
- service.metadata.name = self.service_name # type: ignore[attr-defined]
- service.metadata.resourceVersion = service.metadata.uid = None # type: ignore[attr-defined] # noqa: E501
- client.delete(Service, self._app, namespace=self._namespace)
- client.create(service)
-
- def is_patched(self) -> bool:
- """Reports if the service patch has been applied.
-
- Returns:
- bool: A boolean indicating if the service patch has been applied.
- """
- client = Client()
- return self._is_patched(client)
-
- def _is_patched(self, client: Client) -> bool:
- # Get the relevant service from the cluster
- try:
- service = client.get(Service, name=self.service_name, namespace=self._namespace)
- except ApiError as e:
- if e.status.code == 404 and self.service_name != self._app:
- return False
- logger.error("Kubernetes service get failed: %s", str(e))
- raise
-
- # Construct a list of expected ports, should the patch be applied
- expected_ports = [(p.port, p.targetPort) for p in self.service.spec.ports]
- # Construct a list in the same manner, using the fetched service
- fetched_ports = [
- (p.port, p.targetPort) for p in service.spec.ports # type: ignore[attr-defined]
- ] # noqa: E501
- return expected_ports == fetched_ports
-
- @property
- def _app(self) -> str:
- """Name of the current Juju application.
-
- Returns:
- str: A string containing the name of the current Juju application.
- """
- return self.charm.app.name
-
- @property
- def _namespace(self) -> str:
- """The Kubernetes namespace we're running in.
-
- Returns:
- str: A string containing the name of the current Kubernetes namespace.
- """
- with open("/var/run/secrets/kubernetes.io/serviceaccount/namespace", "r") as f:
- return f.read().strip()
diff --git a/lib/charms/sdcore_nrf/v0/fiveg_nrf.py b/lib/charms/sdcore_nrf_k8s/v0/fiveg_nrf.py
similarity index 97%
rename from lib/charms/sdcore_nrf/v0/fiveg_nrf.py
rename to lib/charms/sdcore_nrf_k8s/v0/fiveg_nrf.py
index 2a35036..8342e0a 100644
--- a/lib/charms/sdcore_nrf/v0/fiveg_nrf.py
+++ b/lib/charms/sdcore_nrf_k8s/v0/fiveg_nrf.py
@@ -13,7 +13,7 @@
From a charm directory, fetch the library using `charmcraft`:
```shell
-charmcraft fetch-lib charms.sdcore_nrf.v0.fiveg_nrf
+charmcraft fetch-lib charms.sdcore_nrf_k8s.v0.fiveg_nrf
```
Add the following libraries to the charm's `requirements.txt` file:
@@ -28,7 +28,7 @@
from ops.charm import CharmBase
from ops.main import main
-from charms.sdcore_nrf.v0.fiveg_nrf import NRFAvailableEvent, NRFRequires
+from charms.sdcore_nrf_k8s.v0.fiveg_nrf import NRFAvailableEvent, NRFRequires
logger = logging.getLogger(__name__)
@@ -58,7 +58,7 @@ def _on_nrf_available(self, event: NRFAvailableEvent):
from ops.charm import CharmBase, RelationJoinedEvent
from ops.main import main
-from charms.sdcore_nrf.v0.fiveg_nrf import NRFProvides
+from charms.sdcore_nrf_k8s.v0.fiveg_nrf import NRFProvides
class DummyFiveGNRFProviderCharm(CharmBase):
@@ -103,14 +103,14 @@ def _on_nrf_url_changed(
from pydantic import AnyHttpUrl, BaseModel, Field, ValidationError
# The unique Charmhub library identifier, never change it
-LIBID = "cd132a12c2b34243bfd2bae8d08c32d6"
+LIBID = "14746bb6f8d34accbeac27ea50ff4715"
# Increment this major API version when introducing breaking changes
LIBAPI = 0
# Increment this PATCH version before using `charmcraft publish-lib` or reset
# to 0 if you are raising the major API version
-LIBPATCH = 6
+LIBPATCH = 1
PYDEPS = ["pydantic", "pytest-interface-tester"]
diff --git a/metadata.yaml b/metadata.yaml
index 7fdc52b..ca8cdf0 100644
--- a/metadata.yaml
+++ b/metadata.yaml
@@ -1,12 +1,12 @@
-name: sdcore-pcf
+name: sdcore-pcf-k8s
-display-name: SD-Core PCF
+display-name: SD-Core PCF K8s
summary: A Charmed Operator for SD-Core's PCF component.
description: |
A Charmed Operator for SD-Core's Policy Control Function (PCF) component.
-website: https://charmhub.io/sdcore-pcf
-source: https://github.com/canonical/sdcore-pcf-operator
-issues: https://github.com/canonical/sdcore-pcf-operator/issues
+website: https://charmhub.io/sdcore-pcf-k8s
+source: https://github.com/canonical/sdcore-pcf-k8s-operator
+issues: https://github.com/canonical/sdcore-pcf-k8s-operator/issues
containers:
pcf:
diff --git a/requirements.txt b/requirements.txt
index 0e404db..31ea8fa 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,6 +1,4 @@
jinja2
-lightkube
-lightkube-models
ops
pydantic
pytest-interface-tester
diff --git a/src/charm.py b/src/charm.py
index 74b1783..8548964 100755
--- a/src/charm.py
+++ b/src/charm.py
@@ -2,7 +2,7 @@
# Copyright 2023 Canonical Ltd.
# See LICENSE file for licensing details.
-"""Charmed operator for the SD-Core's PCF service."""
+"""Charmed operator for the SD-Core's PCF service for K8s."""
import logging
from ipaddress import IPv4Address
@@ -10,10 +10,7 @@
from typing import Optional
from charms.data_platform_libs.v0.data_interfaces import DatabaseRequires # type: ignore[import]
-from charms.observability_libs.v1.kubernetes_service_patch import ( # type: ignore[import] # noqa: E501
- KubernetesServicePatch,
-)
-from charms.sdcore_nrf.v0.fiveg_nrf import NRFRequires # type: ignore[import]
+from charms.sdcore_nrf_k8s.v0.fiveg_nrf import NRFRequires # type: ignore[import]
from charms.tls_certificates_interface.v2.tls_certificates import ( # type: ignore[import]
CertificateAvailableEvent,
CertificateExpiringEvent,
@@ -22,7 +19,6 @@
generate_private_key,
)
from jinja2 import Environment, FileSystemLoader
-from lightkube.models.core_v1 import ServicePort
from ops.charm import CharmBase
from ops.framework import EventBase
from ops.main import main
@@ -46,7 +42,7 @@
class PCFOperatorCharm(CharmBase):
- """Main class to describe Juju event handling for the 5G PCF operator."""
+ """Main class to describe Juju event handling for the 5G PCF operator for K8s."""
def __init__(self, *args):
super().__init__(*args)
@@ -59,12 +55,10 @@ def __init__(self, *args):
self, relation_name="database", database_name=DATABASE_NAME
)
self._nrf_requires = NRFRequires(charm=self, relation_name=NRF_RELATION_NAME)
- self._service_patcher = KubernetesServicePatch(
- charm=self,
- ports=[ServicePort(name="sbi", port=PCF_SBI_PORT)],
- )
+ self.unit.set_ports(PCF_SBI_PORT)
self._certificates = TLSCertificatesRequiresV2(self, "certificates")
self.framework.observe(self.on.database_relation_joined, self._configure_sdcore_pcf)
+ self.framework.observe(self.on.database_relation_broken, self._on_database_relation_broken)
self.framework.observe(self._database.on.database_created, self._configure_sdcore_pcf)
self.framework.observe(self.on.fiveg_nrf_relation_joined, self._configure_sdcore_pcf)
self.framework.observe(self._nrf_requires.on.nrf_available, self._configure_sdcore_pcf)
@@ -133,6 +127,14 @@ def _on_nrf_broken(self, event: EventBase) -> None:
"""
self.unit.status = BlockedStatus("Waiting for fiveg_nrf relation")
+ def _on_database_relation_broken(self, event: EventBase) -> None:
+ """Event handler for database relation broken.
+
+ Args:
+ event: Juju event
+ """
+ self.unit.status = BlockedStatus("Waiting for database relation")
+
def _on_certificates_relation_created(self, event: EventBase) -> None:
"""Generates Private key.
@@ -170,6 +172,9 @@ def _on_certificates_relation_joined(self, event: EventBase) -> None:
if not self._private_key_is_stored():
event.defer()
return
+ if self._certificate_is_stored():
+ return
+
self._request_new_certificate()
def _on_certificate_available(self, event: CertificateAvailableEvent) -> None:
diff --git a/test-requirements.txt b/test-requirements.txt
index 6b5ceb7..c433a2f 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -3,9 +3,11 @@ coverage[toml]
flake8-docstrings
flake8-builtins
isort
+macaroonbakery==1.3.4 # https://protobuf.dev/news/2022-05-06/#python-updates
mypy
pep8-naming
pyproject-flake8
pytest
+pytest-asyncio==0.21.1
pytest-operator
types-PyYAML
diff --git a/tests/integration/test_integration.py b/tests/integration/test_integration.py
index fa0745e..6aba2f0 100644
--- a/tests/integration/test_integration.py
+++ b/tests/integration/test_integration.py
@@ -14,7 +14,7 @@
METADATA = yaml.safe_load(Path("./metadata.yaml").read_text())
APPLICATION_NAME = METADATA["name"]
-NRF_APP_NAME = "sdcore-nrf"
+NRF_APP_NAME = "sdcore-nrf-k8s"
DATABASE_APP_NAME = "mongodb-k8s"
TLS_PROVIDER_NAME = "self-signed-certificates"
@@ -136,3 +136,29 @@ async def test_restore_tls_and_wait_for_active_status(ops_test: OpsTest, build_a
)
await ops_test.model.integrate(relation1=APPLICATION_NAME, relation2=TLS_PROVIDER_NAME)
await ops_test.model.wait_for_idle(apps=[APPLICATION_NAME], status="active", timeout=1000)
+
+
+@pytest.mark.skip(
+ reason="Bug in MongoDB: https://github.com/canonical/mongodb-k8s-operator/issues/218"
+)
+@pytest.mark.abort_on_fail
+async def test_remove_database_and_wait_for_blocked_status(ops_test: OpsTest, build_and_deploy):
+ assert ops_test.model
+ await ops_test.model.remove_application(DATABASE_APP_NAME, block_until_done=True)
+ await ops_test.model.wait_for_idle(apps=[APPLICATION_NAME], status="blocked", timeout=60)
+
+
+@pytest.mark.skip(
+ reason="Bug in MongoDB: https://github.com/canonical/mongodb-k8s-operator/issues/218"
+)
+@pytest.mark.abort_on_fail
+async def test_restore_database_and_wait_for_active_status(ops_test: OpsTest, build_and_deploy):
+ assert ops_test.model
+ await ops_test.model.deploy(
+ DATABASE_APP_NAME,
+ application_name=DATABASE_APP_NAME,
+ channel="5/edge",
+ trust=True,
+ )
+ await ops_test.model.integrate(relation1=APPLICATION_NAME, relation2=DATABASE_APP_NAME)
+ await ops_test.model.wait_for_idle(apps=[APPLICATION_NAME], status="active", timeout=1000)
diff --git a/tests/unit/test_charm.py b/tests/unit/test_charm.py
index bbab6df..0b171c5 100644
--- a/tests/unit/test_charm.py
+++ b/tests/unit/test_charm.py
@@ -3,7 +3,6 @@
import logging
import unittest
-from io import StringIO
from unittest.mock import Mock, PropertyMock, patch
import yaml
@@ -11,7 +10,6 @@
from ops.model import ActiveStatus, BlockedStatus, WaitingStatus
from charm import (
- BASE_CONFIG_PATH,
CONFIG_FILE_NAME,
DATABASE_RELATION_NAME,
NRF_RELATION_NAME,
@@ -26,10 +24,6 @@
class TestCharm(unittest.TestCase):
- @patch(
- "charm.KubernetesServicePatch",
- lambda charm, ports: None,
- )
def setUp(self):
self.maxDiff = None
self.namespace = "whatever"
@@ -82,7 +76,7 @@ def _create_database_relation(self) -> int:
)
return relation_id
- def _database_is_available(self) -> str:
+ def _create_database_relation_and_populate_data(self) -> int:
database_url = "http://1.1.1.1"
database_username = "banana"
database_password = "pizza"
@@ -96,7 +90,7 @@ def _database_is_available(self) -> str:
"uris": "".join([database_url]),
},
)
- return database_url
+ return database_relation_id
def _create_nrf_relation(self) -> int:
"""Creates NRF relation.
@@ -161,24 +155,26 @@ def test_given_container_can_connect_and_certificates_relation_is_not_created_wh
)
@patch("charm.check_output")
- @patch("ops.model.Container.pull")
- @patch("ops.model.Container.exists")
- @patch("charms.sdcore_nrf.v0.fiveg_nrf.NRFRequires.nrf_url", new_callable=PropertyMock)
- @patch("ops.Container.push")
+ @patch("charms.sdcore_nrf_k8s.v0.fiveg_nrf.NRFRequires.nrf_url", new_callable=PropertyMock)
@patch("ops.model.Container.restart")
def test_given_pcf_charm_in_active_state_when_nrf_relation_breaks_then_status_is_blocked(
- self, _, __, patched_nrf_url, patch_exists, patch_pull, patch_check_output
+ self, _, patched_nrf_url, patch_check_output
):
+ self.harness.add_storage(storage_name="config", attach=True)
+ self.harness.add_storage(storage_name="certs", attach=True)
+ root = self.harness.get_filesystem_root("pcf")
+ certificate = "whatever certificate content"
+ (root / "support/TLS/pcf.pem").write_text(certificate)
pod_ip = "1.1.1.1"
patch_check_output.return_value = pod_ip.encode()
- patch_pull.return_value = StringIO("super different config file content")
self.harness.set_can_connect(container=self.container_name, val=True)
patched_nrf_url.return_value = VALID_NRF_URL
nrf_relation_id = self._create_nrf_relation()
- self._database_is_available()
- self.harness.charm._storage_is_attached = Mock(return_value=True)
- patch_exists.return_value = [True, False]
- self.harness.container_pebble_ready("pcf")
+ self._create_database_relation_and_populate_data()
+ self.harness.add_relation(
+ relation_name=TLS_RELATION_NAME, remote_app="tls-certificates-operator"
+ )
+ self.harness.container_pebble_ready(self.container_name)
self.harness.remove_relation(nrf_relation_id)
@@ -187,11 +183,39 @@ def test_given_pcf_charm_in_active_state_when_nrf_relation_breaks_then_status_is
BlockedStatus("Waiting for fiveg_nrf relation"),
)
- @patch("ops.model.Container.push")
+ @patch("charm.check_output")
+ @patch("charms.sdcore_nrf_k8s.v0.fiveg_nrf.NRFRequires.nrf_url", new_callable=PropertyMock)
+ @patch("ops.model.Container.restart")
+ def test_given_pcf_charm_in_active_state_when_database_relation_breaks_then_status_is_blocked(
+ self, _, patched_nrf_url, patch_check_output
+ ):
+ self.harness.add_storage(storage_name="config", attach=True)
+ self.harness.add_storage(storage_name="certs", attach=True)
+ root = self.harness.get_filesystem_root(self.container_name)
+ certificate = "whatever certificate content"
+ (root / "support/TLS/pcf.pem").write_text(certificate)
+ pod_ip = "1.1.1.1"
+ patch_check_output.return_value = pod_ip.encode()
+ self.harness.set_can_connect(container=self.container_name, val=True)
+ patched_nrf_url.return_value = VALID_NRF_URL
+ self._create_nrf_relation()
+ database_relation_id = self._create_database_relation_and_populate_data()
+ self.harness.add_relation(
+ relation_name=TLS_RELATION_NAME, remote_app="tls-certificates-operator"
+ )
+ self.harness.container_pebble_ready(self.container_name)
+
+ self.harness.remove_relation(database_relation_id)
+
+ self.assertEqual(
+ self.harness.model.unit.status,
+ BlockedStatus("Waiting for database relation"),
+ )
+
def test_given_container_can_connect_and_database_relation_is_not_available_when_configure_sdcore_pcf_then_status_is_waiting( # noqa: E501
self,
- patch_push,
):
+ self.harness.add_storage(storage_name="certs", attach=True)
self.harness.set_can_connect(container=self.container_name, val=True)
self._create_database_relation()
self._create_nrf_relation()
@@ -205,13 +229,12 @@ def test_given_container_can_connect_and_database_relation_is_not_available_when
WaitingStatus("Waiting for `database` relation to be available"),
)
- @patch("ops.model.Container.push")
def test_given_container_can_connect_and_fiveg_nrf_relation_is_not_available_when_configure_sdcore_pcf_then_status_is_waiting( # noqa: E501
self,
- patch_push,
):
+ self.harness.add_storage(storage_name="certs", attach=True)
self.harness.set_can_connect(container=self.container_name, val=True)
- self._database_is_available()
+ self._create_database_relation_and_populate_data()
self._create_nrf_relation()
self.harness.add_relation(
relation_name=TLS_RELATION_NAME, remote_app="tls-certificates-operator"
@@ -224,21 +247,19 @@ def test_given_container_can_connect_and_fiveg_nrf_relation_is_not_available_whe
WaitingStatus("Waiting for NRF endpoint to be available"),
)
- @patch("ops.model.Container.push")
- @patch("charms.sdcore_nrf.v0.fiveg_nrf.NRFRequires.nrf_url", new_callable=PropertyMock)
+ @patch("charms.sdcore_nrf_k8s.v0.fiveg_nrf.NRFRequires.nrf_url", new_callable=PropertyMock)
def test_given_container_storage_is_not_attached_when_configure_sdcore_pcf_then_status_is_waiting( # noqa: E501
self,
patched_nrf_url,
- patch_push,
):
+ self.harness.add_storage(storage_name="certs", attach=True)
self.harness.set_can_connect(container=self.container_name, val=True)
patched_nrf_url.return_value = VALID_NRF_URL
- self._database_is_available()
+ self._create_database_relation_and_populate_data()
self._create_nrf_relation()
self.harness.add_relation(
relation_name=TLS_RELATION_NAME, remote_app="tls-certificates-operator"
)
- self.harness.charm._storage_is_attached = Mock(return_value=False)
self.harness.charm._configure_sdcore_pcf(event=Mock())
@@ -247,23 +268,21 @@ def test_given_container_storage_is_not_attached_when_configure_sdcore_pcf_then_
)
@patch("charm.check_output")
- @patch("ops.model.Container.push")
- @patch("charms.sdcore_nrf.v0.fiveg_nrf.NRFRequires.nrf_url", new_callable=PropertyMock)
+ @patch("charms.sdcore_nrf_k8s.v0.fiveg_nrf.NRFRequires.nrf_url", new_callable=PropertyMock)
def test_given_certificate_is_not_stored_when_configure_sdcore_pcf_then_status_is_waiting( # noqa: E501
self,
patched_nrf_url,
- patch_push,
patch_check_output,
):
+ self.harness.add_storage(storage_name="certs", attach=True)
+ self.harness.add_storage(storage_name="config", attach=True)
self.harness.set_can_connect(container=self.container_name, val=True)
patched_nrf_url.return_value = VALID_NRF_URL
- self._database_is_available()
+ self._create_database_relation_and_populate_data()
self._create_nrf_relation()
self.harness.add_relation(
relation_name=TLS_RELATION_NAME, remote_app="tls-certificates-operator"
)
- self.harness.charm._storage_is_attached = Mock(return_value=True)
- self.harness.charm._certificate_is_stored = Mock(return_value=False)
patch_check_output.return_value = b"1.1.1.1"
self.harness.charm._configure_sdcore_pcf(event=Mock())
@@ -273,50 +292,51 @@ def test_given_certificate_is_not_stored_when_configure_sdcore_pcf_then_status_i
)
@patch("charm.check_output")
- @patch("ops.model.Container.exists")
- @patch("ops.Container.push")
- @patch("charms.sdcore_nrf.v0.fiveg_nrf.NRFRequires.nrf_url", new_callable=PropertyMock)
- @patch("ops.model.Container.pull")
+ @patch("charms.sdcore_nrf_k8s.v0.fiveg_nrf.NRFRequires.nrf_url", new_callable=PropertyMock)
def test_given_config_file_is_not_written_when_configure_sdcore_pcf_is_called_then_config_file_is_written_with_expected_content( # noqa: E501
- self, _, patched_nrf_url, patch_push, patch_exists, patch_check_output
+ self, patched_nrf_url, patch_check_output
):
+ self.harness.add_storage(storage_name="certs", attach=True)
+ self.harness.add_storage(storage_name="config", attach=True)
+ root = self.harness.get_filesystem_root(self.container_name)
+ certificate = "whatever certificate content"
+ (root / "support/TLS/pcf.pem").write_text(certificate)
pod_ip = "1.1.1.1"
patch_check_output.return_value = pod_ip.encode()
self.harness.set_can_connect(container=self.container_name, val=True)
patched_nrf_url.return_value = VALID_NRF_URL
- self._database_is_available()
+ self._create_database_relation_and_populate_data()
self._create_nrf_relation()
self.harness.add_relation(
relation_name=TLS_RELATION_NAME, remote_app="tls-certificates-operator"
)
- self.harness.charm._certificate_is_stored = Mock(return_value=True)
- patch_exists.side_effect = [True, False, False]
expected_config_file_content = self._read_file(EXPECTED_CONFIG_FILE_PATH)
self.harness.charm._configure_sdcore_pcf(event=Mock())
- patch_push.assert_called_with(
- path=f"{BASE_CONFIG_PATH}/{CONFIG_FILE_NAME}",
- source=expected_config_file_content.strip(),
+ self.assertEqual(
+ (root / f"etc/pcf/{CONFIG_FILE_NAME}").read_text(),
+ expected_config_file_content.strip(),
)
@patch("charm.check_output")
- @patch("ops.model.Container.pull")
- @patch("ops.model.Container.exists")
- @patch("ops.Container.push")
- @patch("charms.sdcore_nrf.v0.fiveg_nrf.NRFRequires.nrf_url", new_callable=PropertyMock)
+ @patch("charms.sdcore_nrf_k8s.v0.fiveg_nrf.NRFRequires.nrf_url", new_callable=PropertyMock)
def test_given_config_file_is_written_and_is_not_changed_when_configure_sdcore_pcf_is_called_then_config_file_is_not_written( # noqa: E501
- self, patched_nrf_url, patch_push, patch_exists, patch_pull, patch_check_output
+ self, patched_nrf_url, patch_check_output
):
+ self.harness.add_storage(storage_name="certs", attach=True)
+ self.harness.add_storage(storage_name="config", attach=True)
+ root = self.harness.get_filesystem_root(self.container_name)
+ certificate = "whatever certificate content"
+ (root / "support/TLS/pcf.pem").write_text(certificate)
+ (root / f"etc/pcf/{CONFIG_FILE_NAME}").write_text(
+ self._read_file("tests/unit/expected_pcfcfg.yaml").strip()
+ )
+ config_modification_time = (root / f"etc/pcf/{CONFIG_FILE_NAME}").stat().st_mtime
pod_ip = "1.1.1.1"
patch_check_output.return_value = pod_ip.encode()
- patch_pull.side_effect = [
- StringIO(self._read_file(EXPECTED_CONFIG_FILE_PATH)),
- StringIO(self._read_file(EXPECTED_CONFIG_FILE_PATH)),
- ]
patched_nrf_url.return_value = VALID_NRF_URL
- patch_exists.return_value = False
- self._database_is_available()
+ self._create_database_relation_and_populate_data()
self._create_nrf_relation()
self.harness.add_relation(
relation_name=TLS_RELATION_NAME, remote_app="tls-certificates-operator"
@@ -325,59 +345,58 @@ def test_given_config_file_is_written_and_is_not_changed_when_configure_sdcore_p
self.harness.container_pebble_ready("pcf")
- patch_push.assert_not_called()
+ self.assertEqual(
+ (root / f"etc/pcf/{CONFIG_FILE_NAME}").stat().st_mtime, config_modification_time
+ )
- @patch("ops.model.Container.pull")
- @patch("charms.sdcore_nrf.v0.fiveg_nrf.NRFRequires.nrf_url", new_callable=PropertyMock)
- @patch("ops.model.Container.exists")
+ @patch("charms.sdcore_nrf_k8s.v0.fiveg_nrf.NRFRequires.nrf_url", new_callable=PropertyMock)
@patch("charm.check_output")
- @patch("ops.model.Container.push")
def test_given_config_file_exists_and_is_changed_when_configure_pcf_then_config_file_is_updated( # noqa: E501
self,
- patch_push,
patch_check_output,
- patch_exists,
patch_nrf_url,
- patch_pull,
):
+ self.harness.add_storage(storage_name="certs", attach=True)
+ self.harness.add_storage(storage_name="config", attach=True)
+ root = self.harness.get_filesystem_root(self.container_name)
+ certificate = "whatever certificate content"
+ (root / "support/TLS/pcf.pem").write_text(certificate)
+ (root / f"etc/pcf/{CONFIG_FILE_NAME}").write_text("super different config file content")
pod_ip = "1.1.1.1"
patch_check_output.return_value = pod_ip.encode()
- patch_pull.return_value = StringIO("super different config file content")
- patch_exists.side_effect = [True, False, False]
patch_nrf_url.return_value = VALID_NRF_URL
- self._database_is_available()
+ self._create_database_relation_and_populate_data()
self._create_nrf_relation()
self.harness.add_relation(
relation_name=TLS_RELATION_NAME, remote_app="tls-certificates-operator"
)
- self.harness.charm._certificate_is_stored = Mock(return_value=True)
self.harness.set_can_connect(container="pcf", val=True)
self.harness.charm._configure_sdcore_pcf(event=Mock())
- patch_push.assert_called_with(
- path="/etc/pcf/pcfcfg.yaml",
- source=self._read_file("tests/unit/expected_pcfcfg.yaml"),
+ expected_content = self._read_file("tests/unit/expected_pcfcfg.yaml")
+ self.assertEqual(
+ (root / f"etc/pcf/{CONFIG_FILE_NAME}").read_text(), expected_content.strip()
)
- @patch("charms.sdcore_nrf.v0.fiveg_nrf.NRFRequires.nrf_url")
+ @patch("charms.sdcore_nrf_k8s.v0.fiveg_nrf.NRFRequires.nrf_url")
@patch("charm.check_output")
- @patch("ops.model.Container.exists")
- @patch("ops.model.Container.push")
- @patch("ops.model.Container.pull")
def test_given_config_files_and_relations_are_created_when_configure_sdcore_pcf_is_called_then_expected_plan_is_applied( # noqa: E501
- self, _, __, patch_exists, patch_check_output, patch_nrf_url
+ self, patch_check_output, patch_nrf_url
):
+ self.harness.add_storage(storage_name="certs", attach=True)
+ self.harness.add_storage(storage_name="config", attach=True)
+ root = self.harness.get_filesystem_root(self.container_name)
+ certificate = "whatever certificate content"
+ (root / "support/TLS/pcf.pem").write_text(certificate)
pod_ip = "1.1.1.1"
patch_check_output.return_value = pod_ip.encode()
patch_nrf_url.return_value = VALID_NRF_URL
- patch_exists.side_effect = [True, True, False]
- self._database_is_available()
+ self._create_database_relation_and_populate_data()
self._create_nrf_relation()
self.harness.add_relation(
relation_name=TLS_RELATION_NAME, remote_app="tls-certificates-operator"
)
- self.harness.charm._certificate_is_stored = Mock(return_value=True)
self.harness.set_can_connect(container=self.container_name, val=True)
self.harness.charm._configure_sdcore_pcf(event=Mock())
@@ -403,27 +422,26 @@ def test_given_config_files_and_relations_are_created_when_configure_sdcore_pcf_
self.assertEqual(expected_plan, updated_plan)
@patch("charm.check_output")
- @patch("ops.model.Container.pull")
- @patch("ops.model.Container.exists")
- @patch("charms.sdcore_nrf.v0.fiveg_nrf.NRFRequires.nrf_url", new_callable=PropertyMock)
- @patch("ops.Container.push")
+ @patch("charms.sdcore_nrf_k8s.v0.fiveg_nrf.NRFRequires.nrf_url", new_callable=PropertyMock)
@patch("ops.model.Container.restart")
def test_given_config_file_is_written_when_configure_sdcore_pcf_is_called_then_status_is_active( # noqa: E501
- self, _, __, patched_nrf_url, patch_exists, patch_pull, patch_check_output
+ self, _, patched_nrf_url, patch_check_output
):
+ self.harness.add_storage(storage_name="certs", attach=True)
+ self.harness.add_storage(storage_name="config", attach=True)
+ root = self.harness.get_filesystem_root(self.container_name)
+ certificate = "whatever certificate content"
+ (root / "support/TLS/pcf.pem").write_text(certificate)
+ (root / f"etc/pcf/{CONFIG_FILE_NAME}").write_text("super different config file content")
pod_ip = "1.1.1.1"
patch_check_output.return_value = pod_ip.encode()
- patch_pull.return_value = StringIO("super different config file content")
self.harness.set_can_connect(container=self.container_name, val=True)
patched_nrf_url.return_value = VALID_NRF_URL
self._create_nrf_relation()
- self._database_is_available()
+ self._create_database_relation_and_populate_data()
self.harness.add_relation(
relation_name=TLS_RELATION_NAME, remote_app="tls-certificates-operator"
)
- self.harness.charm._certificate_is_stored = Mock(return_value=True)
- self.harness.charm._storage_is_attached = Mock(return_value=True)
- patch_exists.return_value = [True, False]
self.harness.charm._configure_sdcore_pcf(event=Mock())
@@ -431,22 +449,22 @@ def test_given_config_file_is_written_when_configure_sdcore_pcf_is_called_then_s
@patch("ops.model.Container.restart", new=Mock)
@patch("charm.check_output")
- @patch("ops.model.Container.pull", new=Mock)
- @patch("ops.model.Container.exists")
- @patch("ops.Container.push", new=Mock)
- @patch("charms.sdcore_nrf.v0.fiveg_nrf.NRFRequires.nrf_url", new_callable=PropertyMock)
+ @patch("charms.sdcore_nrf_k8s.v0.fiveg_nrf.NRFRequires.nrf_url", new_callable=PropertyMock)
def test_given_ip_not_available_when_configure_then_status_is_waiting(
- self, _, patch_exists, patch_check_output
+ self, _, patch_check_output
):
+ self.harness.add_storage(storage_name="certs", attach=True)
+ self.harness.add_storage(storage_name="config", attach=True)
+ root = self.harness.get_filesystem_root(self.container_name)
+ certificate = "whatever certificate content"
+ (root / "support/TLS/pcf.pem").write_text(certificate)
patch_check_output.return_value = "".encode()
self._create_nrf_relation()
- self._database_is_available()
- self.harness.charm._storage_is_attached = Mock(return_value=True)
- patch_exists.return_value = [True, False]
+ self._create_database_relation_and_populate_data()
self.harness.add_relation(
relation_name=TLS_RELATION_NAME, remote_app="tls-certificates-operator"
)
- self.harness.container_pebble_ready(container_name="pcf")
+ self.harness.container_pebble_ready(container_name=self.container_name)
self.assertEqual(
self.harness.model.unit.status,
@@ -454,89 +472,108 @@ def test_given_ip_not_available_when_configure_then_status_is_waiting(
)
@patch("charm.generate_private_key")
- @patch("ops.model.Container.push")
def test_given_can_connect_when_on_certificates_relation_created_then_private_key_is_generated(
- self, patch_push, patch_generate_private_key
+ self, patch_generate_private_key
):
+ self.harness.add_storage(storage_name="certs", attach=True)
+ self.harness.add_storage(storage_name="config", attach=True)
+ root = self.harness.get_filesystem_root(self.container_name)
private_key = b"whatever key content"
- self.harness.set_can_connect(container="pcf", val=True)
+ self.harness.set_can_connect(container=self.container_name, val=True)
patch_generate_private_key.return_value = private_key
self.harness.charm._on_certificates_relation_created(event=Mock)
- patch_push.assert_called_with(path="/support/TLS/pcf.key", source=private_key.decode())
+ self.assertEqual((root / "support/TLS/pcf.key").read_text(), private_key.decode())
- @patch("ops.model.Container.remove_path")
- @patch("ops.model.Container.exists")
def test_given_certificates_are_stored_when_on_certificates_relation_broken_then_certificates_are_removed( # noqa: E501
- self, patch_exists, patch_remove_path
+ self,
):
- patch_exists.return_value = True
- self.harness.set_can_connect(container="pcf", val=True)
+ self.harness.add_storage(storage_name="certs", attach=True)
+ self.harness.set_can_connect(container=self.container_name, val=True)
+ root = self.harness.get_filesystem_root(self.container_name)
+ private_key = "whatever key content"
+ csr = "Whatever CSR content"
+ certificate = "Whatever certificate content"
+ (root / "support/TLS/pcf.key").write_text(private_key)
+ (root / "support/TLS/pcf.csr").write_text(csr)
+ (root / "support/TLS/pcf.pem").write_text(certificate)
self.harness.charm._on_certificates_relation_broken(event=Mock)
- patch_remove_path.assert_any_call(path="/support/TLS/pcf.pem")
- patch_remove_path.assert_any_call(path="/support/TLS/pcf.key")
- patch_remove_path.assert_any_call(path="/support/TLS/pcf.csr")
+ with self.assertRaises(FileNotFoundError):
+ (root / "support/TLS/pcf.pem").read_text()
+ (root / "support/TLS/pcf.key").read_text()
+ (root / "support/TLS/pcf.csr").read_text()
@patch(
"charms.tls_certificates_interface.v2.tls_certificates.TLSCertificatesRequiresV2.request_certificate_creation", # noqa: E501
new=Mock,
)
- @patch("ops.model.Container.push")
@patch("charm.generate_csr")
- @patch("ops.model.Container.pull")
- @patch("ops.model.Container.exists")
def test_given_private_key_exists_when_on_certificates_relation_joined_then_csr_is_generated(
- self, patch_exists, patch_pull, patch_generate_csr, patch_push
+ self, patch_generate_csr
):
+ self.harness.add_storage(storage_name="certs", attach=True)
+ root = self.harness.get_filesystem_root(self.container_name)
+ private_key = "whatever key content"
+ (root / "support/TLS/pcf.key").write_text(private_key)
csr = b"whatever csr content"
patch_generate_csr.return_value = csr
- patch_pull.return_value = StringIO("private key content")
- patch_exists.return_value = True
- self.harness.set_can_connect(container="pcf", val=True)
+ self.harness.set_can_connect(container=self.container_name, val=True)
self.harness.charm._on_certificates_relation_joined(event=Mock)
- patch_push.assert_called_with(path="/support/TLS/pcf.csr", source=csr.decode())
+ self.assertEqual((root / "support/TLS/pcf.csr").read_text(), csr.decode())
@patch(
"charms.tls_certificates_interface.v2.tls_certificates.TLSCertificatesRequiresV2.request_certificate_creation", # noqa: E501
)
- @patch("ops.model.Container.push", new=Mock)
@patch("charm.generate_csr")
- @patch("ops.model.Container.pull")
- @patch("ops.model.Container.exists")
- def test_given_private_key_exists_when_on_certificates_relation_joined_then_cert_is_requested(
+ def test_given_private_key_exists_and_cert_not_yet_requested_when_on_certificates_relation_joined_then_cert_is_requested( # noqa: E501
self,
- patch_exists,
- patch_pull,
patch_generate_csr,
patch_request_certificate_creation,
):
+ self.harness.add_storage(storage_name="certs", attach=True)
+ root = self.harness.get_filesystem_root(self.container_name)
+ private_key = "whatever key content"
+ (root / "support/TLS/pcf.key").write_text(private_key)
csr = b"whatever csr content"
patch_generate_csr.return_value = csr
- patch_pull.return_value = StringIO("private key content")
- patch_exists.return_value = True
self.harness.set_can_connect(container="pcf", val=True)
self.harness.charm._on_certificates_relation_joined(event=Mock)
patch_request_certificate_creation.assert_called_with(certificate_signing_request=csr)
- @patch("ops.model.Container.pull")
- @patch("ops.model.Container.exists")
- @patch("ops.model.Container.push")
+ @patch(
+ "charms.tls_certificates_interface.v2.tls_certificates.TLSCertificatesRequiresV2.request_certificate_creation", # noqa: E501
+ )
+ def test_given_cert_already_stored_when_on_certificates_relation_joined_then_cert_is_not_requested( # noqa: E501
+ self, patch_request_certificate_creation
+ ):
+ self.harness.add_storage(storage_name="certs", attach=True)
+ root = self.harness.get_filesystem_root(self.container_name)
+ private_key = "whatever key content"
+ (root / "support/TLS/pcf.key").write_text(private_key)
+ certificate = "whatever certificate content"
+ (root / "support/TLS/pcf.pem").write_text(certificate)
+ self.harness.set_can_connect(container="pcf", val=True)
+
+ self.harness.charm._on_certificates_relation_joined(event=Mock)
+
+ patch_request_certificate_creation.assert_not_called()
+
def test_given_csr_matches_stored_one_when_certificate_available_then_certificate_is_pushed(
self,
- patch_push,
- patch_exists,
- patch_pull,
):
+ self.harness.add_storage(storage_name="certs", attach=True)
+ root = self.harness.get_filesystem_root(self.container_name)
+ private_key = "whatever key content"
csr = "Whatever CSR content"
- patch_pull.return_value = StringIO(csr)
- patch_exists.return_value = True
+ (root / "support/TLS/pcf.key").write_text(private_key)
+ (root / "support/TLS/pcf.csr").write_text(csr)
certificate = "Whatever certificate content"
event = Mock()
event.certificate = certificate
@@ -545,19 +582,17 @@ def test_given_csr_matches_stored_one_when_certificate_available_then_certificat
self.harness.charm._on_certificate_available(event=event)
- patch_push.assert_called_with(path="/support/TLS/pcf.pem", source=certificate)
+ self.assertEqual((root / "support/TLS/pcf.pem").read_text(), certificate)
- @patch("ops.model.Container.pull")
- @patch("ops.model.Container.exists")
- @patch("ops.model.Container.push")
def test_given_csr_doesnt_match_stored_one_when_certificate_available_then_certificate_is_not_pushed( # noqa: E501
self,
- patch_push,
- patch_exists,
- patch_pull,
):
- patch_pull.return_value = StringIO("Stored CSR content")
- patch_exists.return_value = True
+ self.harness.add_storage(storage_name="certs", attach=True)
+ root = self.harness.get_filesystem_root(self.container_name)
+ private_key = "whatever key content"
+ csr = "Stored CSR content"
+ (root / "support/TLS/pcf.key").write_text(private_key)
+ (root / "support/TLS/pcf.csr").write_text(csr)
certificate = "Whatever certificate content"
event = Mock()
event.certificate = certificate
@@ -566,19 +601,21 @@ def test_given_csr_doesnt_match_stored_one_when_certificate_available_then_certi
self.harness.charm._on_certificate_available(event=event)
- patch_push.assert_not_called()
+ with self.assertRaises(FileNotFoundError):
+ (root / "support/TLS/pcf.pem").read_text()
@patch(
"charms.tls_certificates_interface.v2.tls_certificates.TLSCertificatesRequiresV2.request_certificate_creation", # noqa: E501
)
- @patch("ops.model.Container.push", new=Mock)
@patch("charm.generate_csr")
- @patch("ops.model.Container.pull")
def test_given_certificate_does_not_match_stored_one_when_certificate_expiring_then_certificate_is_not_requested( # noqa: E501
- self, patch_pull, patch_generate_csr, patch_request_certificate_creation
+ self, patch_generate_csr, patch_request_certificate_creation
):
+ self.harness.add_storage(storage_name="certs", attach=True)
+ root = self.harness.get_filesystem_root(self.container_name)
+ certificate = "Stored certificate content"
+ (root / "support/TLS/pcf.pem").write_text(certificate)
event = Mock()
- patch_pull.return_value = StringIO("Stored certificate content")
event.certificate = "Relation certificate content (different from stored)"
csr = b"whatever csr content"
patch_generate_csr.return_value = csr
@@ -591,16 +628,18 @@ def test_given_certificate_does_not_match_stored_one_when_certificate_expiring_t
@patch(
"charms.tls_certificates_interface.v2.tls_certificates.TLSCertificatesRequiresV2.request_certificate_creation", # noqa: E501
)
- @patch("ops.model.Container.push", new=Mock)
@patch("charm.generate_csr")
- @patch("ops.model.Container.pull")
def test_given_certificate_matches_stored_one_when_certificate_expiring_then_certificate_is_requested( # noqa: E501
- self, patch_pull, patch_generate_csr, patch_request_certificate_creation
+ self, patch_generate_csr, patch_request_certificate_creation
):
+ self.harness.add_storage(storage_name="certs", attach=True)
+ root = self.harness.get_filesystem_root(self.container_name)
+ private_key = "whatever key content"
certificate = "whatever certificate content"
+ (root / "support/TLS/pcf.key").write_text(private_key)
+ (root / "support/TLS/pcf.pem").write_text(certificate)
event = Mock()
event.certificate = certificate
- patch_pull.return_value = StringIO(certificate)
csr = b"whatever csr content"
patch_generate_csr.return_value = csr
self.harness.set_can_connect(container="pcf", val=True)