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

refactor is_active check and add update_status hook #25

Merged
merged 13 commits into from
Mar 21, 2024
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: 2 additions & 0 deletions .github/workflows/integration_test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,5 @@ jobs:
-c "sudo microk8s config > ${GITHUB_WORKSPACE}/kube-config
chmod +x tests/integration/pre_run_script.sh
./tests/integration/pre_run_script.sh"
juju-channel: 3.1/stable
channel: 1.27-strict/stable
2 changes: 1 addition & 1 deletion src-docs/charm.py.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ Unit that this execution is responsible for.

---

<a href="../src/charm.py#L73"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
<a href="../src/charm.py#L74"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>

### <kbd>function</kbd> `restart_agent_service`

Expand Down
27 changes: 14 additions & 13 deletions src-docs/service.py.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ The agent pebble service module.
**Global Variables**
---------------
- **AGENT_SERVICE_NAME**
- **AGENT_PACKAGE_NAME**
- **APT_PACKAGE_NAME**
- **APT_PACKAGE_VERSION**
- **SYSTEMD_SERVICE_CONF_DIR**
- **PPA_URI**
- **PPA_DEB_SRC**
Expand All @@ -33,7 +34,7 @@ Jenkins agent service class.

Attrs: is_active: Indicate if the agent service is active and running.

<a href="../src/service.py#L54"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
<a href="../src/service.py#L55"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>

### <kbd>function</kbd> `__init__`

Expand All @@ -60,7 +61,7 @@ Indicate if the jenkins agent service is active.

---

<a href="../src/service.py#L95"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
<a href="../src/service.py#L98"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>

### <kbd>function</kbd> `install`

Expand All @@ -78,39 +79,39 @@ Install and set up the jenkins agent apt package.

---

<a href="../src/service.py#L123"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
<a href="../src/service.py#L169"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>

### <kbd>function</kbd> `restart`
### <kbd>function</kbd> `reset`

```python
restart() → None
reset() → None
```

Start the agent service.
Stop the agent service and clear its configuration file.



**Raises:**

- <b>`ServiceRestartError`</b>: when restarting the service fails
- <b>`ServiceStopError`</b>: if systemctl stop returns a non-zero exit code.

---

<a href="../src/service.py#L166"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>
<a href="../src/service.py#L126"><img align="right" style="float:right;" src="https://img.shields.io/badge/-source-cccccc?style=flat-square"></a>

### <kbd>function</kbd> `stop`
### <kbd>function</kbd> `restart`

```python
stop() → None
restart() → None
```

Stop the agent service.
Start the agent service.



**Raises:**

- <b>`ServiceStopError`</b>: if systemctl stop returns a non-zero exit code.
- <b>`ServiceRestartError`</b>: when restarting the service fails


---
Expand Down
17 changes: 17 additions & 0 deletions src/charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ def __init__(self, *args: typing.Any):
self.framework.observe(self.on.start, self._on_start)
self.framework.observe(self.on.config_changed, self._on_config_changed)
self.framework.observe(self.on.upgrade_charm, self._on_upgrade_charm)
self.framework.observe(self.on.update_status, self._on_update_status)
Thanhphan1147 marked this conversation as resolved.
Show resolved Hide resolved

def _on_install(self, _: ops.InstallEvent) -> None:
"""Handle install event, setup the agent service.
Expand Down Expand Up @@ -94,6 +95,22 @@ def restart_agent_service(self) -> None:

self.model.unit.status = ops.ActiveStatus()

def _on_update_status(self, _: ops.UpdateStatusEvent) -> None:
"""Update status event hook.

Raises:
RuntimeError: when the service is not running.
"""
if self.model.get_relation(AGENT_RELATION) and not self.jenkins_agent_service.is_active:
logger.error("agent related to Jenkins but service is not active")
raise RuntimeError("jenkins-agent service is not running")
jdkandersson marked this conversation as resolved.
Show resolved Hide resolved

if not self.model.get_relation(AGENT_RELATION):
self.model.unit.status = ops.BlockedStatus("Waiting for relation.")
return

self.model.unit.status = ops.ActiveStatus()


if __name__ == "__main__": # pragma: no cover
main(JenkinsAgentCharm)
15 changes: 8 additions & 7 deletions src/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@

logger = logging.getLogger(__name__)
AGENT_SERVICE_NAME = "jenkins-agent"
APT_PACKAGE_NAME = "jenkins-agent"
APT_PACKAGE_VERSION = "1.0.6"
APT_PACKAGE_VERSION = "1.0.8"
APT_PACKAGE_NAME = f"jenkins-agent-{APT_PACKAGE_VERSION}"
SYSTEMD_SERVICE_CONF_DIR = "/etc/systemd/system/jenkins-agent.service.d/"
PPA_URI = "https://ppa.launchpadcontent.net/canonical-is-devops/jenkins-agent-charm/ubuntu/"
PPA_DEB_SRC = "deb-https://ppa.launchpadcontent.net/canonical-is-devops/jenkins-agent-charm/ubuntu/-" # noqa: E501 pylint: disable=line-too-long
Expand Down Expand Up @@ -88,7 +88,9 @@ def _render_file(self, path: Path, content: str, mode: int) -> None:
def is_active(self) -> bool:
"""Indicate if the jenkins agent service is active."""
try:
return systemd.service_running(AGENT_SERVICE_NAME)
return os.path.exists(str(AGENT_READY_PATH)) and systemd.service_running(
Thanhphan1147 marked this conversation as resolved.
Show resolved Hide resolved
AGENT_SERVICE_NAME
)
except SystemError as exc:
logger.error("Failed to call systemctl:\n%s", exc)
return False
Expand Down Expand Up @@ -117,7 +119,7 @@ def install(self) -> None:
# Install the necessary packages
apt.update()
apt.add_package("openjdk-17-jre")
apt.add_package(package_names=APT_PACKAGE_NAME, version=APT_PACKAGE_VERSION)
apt.add_package(package_names=APT_PACKAGE_NAME)
except (apt.PackageError, apt.PackageNotFoundError, apt.GPGKeyError) as exc:
raise PackageInstallError("Error installing the agent package") from exc

Expand Down Expand Up @@ -187,7 +189,6 @@ def _startup_check(self) -> bool:
timeout = time.time() + STARTUP_CHECK_TIMEOUT
while time.time() < timeout:
time.sleep(STARTUP_CHECK_INTERVAL)
service_up = os.path.exists(str(AGENT_READY_PATH)) and self.is_active
if service_up:
if self.is_active:
break
return os.path.exists(str(AGENT_READY_PATH)) and self.is_active
return self.is_active
2 changes: 1 addition & 1 deletion tests/integration/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ async def jenkins_server_model_fixture(
@pytest_asyncio.fixture(scope="function", name="jenkins_server")
async def jenkins_server_fixture(jenkins_server_model: Model) -> Application:
"""The jenkins machine server."""
jenkins = await jenkins_server_model.deploy("jenkins-k8s")
jenkins = await jenkins_server_model.deploy("jenkins-k8s", channel="latest/edge")
await jenkins_server_model.wait_for_idle(
apps=[jenkins.name],
timeout=20 * 60,
Expand Down
8 changes: 4 additions & 4 deletions tests/integration/pre_run_script.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@
# Jenkins machine agent charm is deployed on lxd and Jenkins-k8s server charm is deployed on
# microk8s.

sg microk8s -c "microk8s status --wait-ready"
sg snap_microk8s -c "microk8s status --wait-ready"
# lxd should be installed and inited by a previous step in integration test action.
echo "bootstrapping lxd juju controller"
sg microk8s -c "juju bootstrap localhost localhost"
sg snap_microk8s -c "juju bootstrap localhost localhost"

echo "bootstrapping secondary microk8s controller"
sg microk8s -c "juju bootstrap microk8s controller"
sg snap_microk8s -c "juju bootstrap microk8s controller"

echo "Switching to testing model"
sg microk8s -c "juju switch localhost"
sg snap_microk8s -c "juju switch localhost"
2 changes: 1 addition & 1 deletion tests/integration/requirements_integration_tests.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
jenkinsapi>=0.3,<1
juju==3.0.4
juju>=3.2.2
ops
pytest
pytest-asyncio
Expand Down
53 changes: 51 additions & 2 deletions tests/unit/test_charm.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

"""Test for charm hooks."""

from unittest.mock import MagicMock
from unittest.mock import MagicMock, PropertyMock

import ops
import ops.testing
Expand Down Expand Up @@ -41,7 +41,7 @@ def test__on_upgrade_charm(harness: ops.testing.Harness, monkeypatch: pytest.Mon
act: when _on_upgrade_charm is called.
assert: The agent falls into waiting status with the correct message.
"""
monkeypatch.setattr(service.JenkinsAgentService, "is_active", MagicMock(return_value=True))
monkeypatch.setattr(service.JenkinsAgentService, "is_active", PropertyMock(return_value=True))
monkeypatch.setattr(service.JenkinsAgentService, "restart", MagicMock())
harness.begin()

Expand Down Expand Up @@ -129,3 +129,52 @@ def test_restart_agent_service_service_restart_error(
monkeypatch.setattr(charm.state, "agent_relation_credentials", get_credentials_mock)
with pytest.raises(RuntimeError, match="Error restarting the agent service"):
charm.restart_agent_service()


def test_update_status_service_not_active(
harness: ops.testing.Harness, monkeypatch: pytest.MonkeyPatch
):
"""
arrange: given a charm with relation to jenkins and the service is not active.
act: when update-status hook is fired.
assert: The charm correctly raise a runtime error.
"""
harness.add_relation(charm_state.AGENT_RELATION, "jenkins-k8s")
monkeypatch.setattr(service.JenkinsAgentService, "is_active", PropertyMock(return_value=False))
harness.begin()

with pytest.raises(RuntimeError, match="jenkins-agent service is not running"):
harness.charm.on.update_status.emit()


def test_update_status_service_waiting_for_relation(
harness: ops.testing.Harness, monkeypatch: pytest.MonkeyPatch
):
"""
arrange: given a charm without a relation to jenkins.
act: when update-status hook is fired.
assert: The charm correctly sets the status to blocked.
"""
monkeypatch.setattr(service.JenkinsAgentService, "is_active", PropertyMock(return_value=False))
harness.begin()

harness.charm.on.update_status.emit()

assert harness.charm.unit.status.name == ops.BlockedStatus.name


def test_update_status_service_active(
harness: ops.testing.Harness, monkeypatch: pytest.MonkeyPatch
):
"""
arrange: given a charm with relation to jenkins and the service is active.
act: when update-status hook is fired.
assert: The charm correctly sets the status to active.
"""
harness.add_relation(charm_state.AGENT_RELATION, "jenkins-k8s")
monkeypatch.setattr(service.JenkinsAgentService, "is_active", PropertyMock(return_value=True))
harness.begin()

harness.charm.on.update_status.emit()

assert harness.charm.unit.status.name == ops.ActiveStatus.name
1 change: 0 additions & 1 deletion tests/unit/test_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,6 @@ def test_on_install(harness: ops.testing.Harness, monkeypatch: pytest.MonkeyPatc
assert apt_add_package_mock.call_count == 2
assert apt_add_package_mock.call_args_list[0][0][0] == "openjdk-17-jre"
assert apt_add_package_mock.call_args_list[1][1]["package_names"] == service.APT_PACKAGE_NAME
assert apt_add_package_mock.call_args_list[1][1]["version"] == service.APT_PACKAGE_VERSION

assert harness.charm.unit.status.name == ops.BlockedStatus.name

Expand Down
Loading