Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

parse controller API port from apiaddresses in agent.conf #54

Merged
merged 1 commit into from
Nov 9, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion run_tests
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ if [ -n "$PYTHONPATH" ]; then
fi
export PYTHONPATH="src:lib$PYTHONPATH"

flake8
coverage run --source=src -m unittest -v "$@"
coverage report -m
flake8
27 changes: 24 additions & 3 deletions src/charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,13 +5,15 @@
import controlsocket
import logging
import secrets
import urllib.parse
import yaml
from charms.prometheus_k8s.v0.prometheus_scrape import MetricsEndpointProvider
from ops.charm import CharmBase
from ops.framework import StoredState
from ops.charm import RelationJoinedEvent, RelationDepartedEvent
from ops.main import main
from ops.model import ActiveStatus, BlockedStatus, Relation
from ops.model import ActiveStatus, BlockedStatus, ErrorStatus, Relation
from typing import List

logger = logging.getLogger(__name__)

Expand Down Expand Up @@ -79,14 +81,20 @@ def _on_metrics_endpoint_relation_created(self, event: RelationJoinedEvent):
self.control_socket.add_metrics_user(username, password)

# Set up Prometheus scrape config
try:
api_port = self.api_port()
except AgentConfException as e:
self.unit.status = ErrorStatus(f"can't read controller API port from agent.conf: {e}")
return

metrics_endpoint = MetricsEndpointProvider(
self,
jobs=[{
"metrics_path": "/introspection/metrics",
"scheme": "https",
"static_configs": [{
"targets": [
f'*:{self.api_port()}'
f'*:{api_port}'
]
}],
"basic_auth": {
Expand Down Expand Up @@ -116,7 +124,16 @@ def _agent_conf(self, key: str):

def api_port(self) -> str:
"""Return the port on which the controller API server is listening."""
return self._agent_conf('apiport')
api_addresses = self._agent_conf('apiaddresses')
if not api_addresses:
raise AgentConfException("agent.conf key 'apiaddresses' missing")
if not isinstance(api_addresses, List):
raise AgentConfException("agent.conf key 'apiaddresses' is not a list")

parsed_url = urllib.parse.urlsplit('//' + api_addresses[0])
if not parsed_url.port:
raise AgentConfException("api address doesn't include port")
return parsed_url.port

def ca_cert(self) -> str:
"""Return the controller's CA certificate."""
Expand All @@ -136,5 +153,9 @@ def generate_password() -> str:
return secrets.token_urlsafe(16)


class AgentConfException(Exception):
"""Raised when there are errors reading info from agent.conf."""


if __name__ == "__main__":
main(JujuControllerCharm)
76 changes: 74 additions & 2 deletions tests/test_charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,36 @@

import os
import unittest
from charm import JujuControllerCharm
from charm import JujuControllerCharm, AgentConfException
from ops import ErrorStatus
from ops.testing import Harness
from unittest.mock import mock_open, patch

agent_conf = '''
apiport: 17070
apiaddresses:
- localhost:17070
cacert: fake
'''

agent_conf_apiaddresses_missing = '''
cacert: fake
'''

agent_conf_apiaddresses_not_list = '''
apiaddresses:
foo: bar
cacert: fake
'''

agent_conf_ipv4 = '''
apiaddresses:
- "127.0.0.1:17070"
cacert: fake
'''

agent_conf_ipv6 = '''
apiaddresses:
- "[::1]:17070"
cacert: fake
'''

Expand Down Expand Up @@ -88,6 +112,54 @@ def test_metrics_endpoint_relation(self, mock_remove_user, mock_add_user,
harness.remove_relation(relation_id)
mock_remove_user.assert_called_once_with(f'juju-metrics-r{relation_id}')

@patch("builtins.open", new_callable=mock_open, read_data=agent_conf_apiaddresses_missing)
def test_apiaddresses_missing(self, _):
harness = Harness(JujuControllerCharm)
self.addCleanup(harness.cleanup)
harness.begin()

with self.assertRaisesRegex(AgentConfException, "agent.conf key 'apiaddresses' missing"):
harness.charm.api_port()

@patch("builtins.open", new_callable=mock_open, read_data=agent_conf_apiaddresses_not_list)
def test_apiaddresses_not_list(self, _):
harness = Harness(JujuControllerCharm)
self.addCleanup(harness.cleanup)
harness.begin()

with self.assertRaisesRegex(
AgentConfException, "agent.conf key 'apiaddresses' is not a list"
):
harness.charm.api_port()

@patch("builtins.open", new_callable=mock_open, read_data=agent_conf_apiaddresses_missing)
@patch("controlsocket.Client.add_metrics_user")
def test_apiaddresses_missing_status(self, *_):
harness = Harness(JujuControllerCharm)
self.addCleanup(harness.cleanup)
harness.begin()

harness.add_relation('metrics-endpoint', 'prometheus-k8s')
self.assertEqual(harness.charm.unit.status, ErrorStatus(
"can't read controller API port from agent.conf: agent.conf key 'apiaddresses' missing"
))

@patch("builtins.open", new_callable=mock_open, read_data=agent_conf_ipv4)
def test_apiaddresses_ipv4(self, _):
harness = Harness(JujuControllerCharm)
self.addCleanup(harness.cleanup)
harness.begin()

self.assertEqual(harness.charm.api_port(), 17070)

@patch("builtins.open", new_callable=mock_open, read_data=agent_conf_ipv6)
def test_apiaddresses_ipv6(self, _):
harness = Harness(JujuControllerCharm)
self.addCleanup(harness.cleanup)
harness.begin()

self.assertEqual(harness.charm.api_port(), 17070)


class MockBinding:
def __init__(self, address):
Expand Down
Loading