From e106f0478389d22831a64af1904f60e8a6d1f35b Mon Sep 17 00:00:00 2001 From: Mia Altieri Date: Mon, 20 Nov 2023 17:17:38 +0000 Subject: [PATCH 1/4] charmcraft init --- .gitignore | 9 +++ CONTRIBUTING.md | 34 +++++++++++ README.md | 26 ++++++++ charmcraft.yaml | 11 ++++ config.yaml | 14 +++++ metadata.yaml | 43 +++++++++++++ pyproject.toml | 46 ++++++++++++++ requirements.txt | 1 + src/charm.py | 103 ++++++++++++++++++++++++++++++++ tests/integration/test_charm.py | 35 +++++++++++ tests/unit/test_charm.py | 68 +++++++++++++++++++++ tox.ini | 86 ++++++++++++++++++++++++++ 12 files changed, 476 insertions(+) create mode 100644 .gitignore create mode 100644 CONTRIBUTING.md create mode 100644 README.md create mode 100644 charmcraft.yaml create mode 100644 config.yaml create mode 100644 metadata.yaml create mode 100644 pyproject.toml create mode 100644 requirements.txt create mode 100755 src/charm.py create mode 100644 tests/integration/test_charm.py create mode 100644 tests/unit/test_charm.py create mode 100644 tox.ini diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..a26d707f --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +venv/ +build/ +*.charm +.tox/ +.coverage +__pycache__/ +*.py[cod] +.idea +.vscode/ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 00000000..20e88bcc --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,34 @@ +# Contributing + +To make contributions to this charm, you'll need a working [development setup](https://juju.is/docs/sdk/dev-setup). + +You can create an environment for development with `tox`: + +```shell +tox devenv -e integration +source venv/bin/activate +``` + +## Testing + +This project uses `tox` for managing test environments. There are some pre-configured environments +that can be used for linting and formatting code when you're preparing contributions to the charm: + +```shell +tox run -e format # update your code according to linting rules +tox run -e lint # code style +tox run -e static # static type checking +tox run -e unit # unit tests +tox run -e integration # integration tests +tox # runs 'format', 'lint', 'static', and 'unit' environments +``` + +## Build the charm + +Build the charm in this git repository using: + +```shell +charmcraft pack +``` + + + +# mongos-operator + +Charmhub package name: operator-template +More information: https://charmhub.io/mongos-operator + +Describe your charm in one or two sentences. + +## Other resources + + + +- [Read more](https://example.com) + +- [Contributing](CONTRIBUTING.md) + +- See the [Juju SDK documentation](https://juju.is/docs/sdk) for more information about developing and improving charms. diff --git a/charmcraft.yaml b/charmcraft.yaml new file mode 100644 index 00000000..900d34a2 --- /dev/null +++ b/charmcraft.yaml @@ -0,0 +1,11 @@ +# This file configures Charmcraft. +# See https://juju.is/docs/sdk/charmcraft-config for guidance. + +type: charm +bases: + - build-on: + - name: ubuntu + channel: "22.04" + run-on: + - name: ubuntu + channel: "22.04" diff --git a/config.yaml b/config.yaml new file mode 100644 index 00000000..26b5b563 --- /dev/null +++ b/config.yaml @@ -0,0 +1,14 @@ +# This file defines charm config options, and populates the Configure tab on Charmhub. +# If your charm does not require configuration options, delete this file entirely. +# +# See https://juju.is/docs/config for guidance. + +options: + # An example config option to customise the log level of the workload + log-level: + description: | + Configures the log level of gunicorn. + + Acceptable values are: "info", "debug", "warning", "error" and "critical" + default: "info" + type: string diff --git a/metadata.yaml b/metadata.yaml new file mode 100644 index 00000000..80c2a848 --- /dev/null +++ b/metadata.yaml @@ -0,0 +1,43 @@ +# This file populates the Overview on Charmhub. +# See https://juju.is/docs/sdk/metadata-reference for a checklist and guidance. + +# The charm package name, no spaces (required) +# See https://juju.is/docs/sdk/naming#heading--naming-charms for guidance. +name: mongos-operator + +# The following metadata are human-readable and will be published prominently on Charmhub. + +# (Recommended) +display-name: Charm Template + +# (Required) +summary: A very short one-line summary of the charm. + +description: | + A single sentence that says what the charm is, concisely and memorably. + + A paragraph of one to three short sentences, that describe what the charm does. + + A third paragraph that explains what need the charm meets. + + Finally, a paragraph that describes whom the charm is useful for. + +# The containers and resources metadata apply to Kubernetes charms only. +# Remove them if not required. + +# Your workload’s containers. +containers: + httpbin: + resource: httpbin-image + +# This field populates the Resources tab on Charmhub. +resources: + # An OCI image resource for each container listed above. + # You may remove this if your charm will run without a workload sidecar container. + httpbin-image: + type: oci-image + description: OCI image for httpbin + # The upstream-source field is ignored by Juju. It is included here as a reference + # so the integration testing suite knows which image to deploy during testing. This field + # is also used by the 'canonical/charming-actions' Github action for automated releasing. + upstream-source: kennethreitz/httpbin diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..e10531c7 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,46 @@ +# Testing tools configuration +[tool.coverage.run] +branch = true + +[tool.coverage.report] +show_missing = true + +[tool.pytest.ini_options] +minversion = "6.0" +log_cli_level = "INFO" + +# Formatting tools configuration +[tool.black] +line-length = 99 +target-version = ["py38"] + +# Linting tools configuration +[tool.ruff] +line-length = 99 +select = ["E", "W", "F", "C", "N", "D", "I001"] +extend-ignore = [ + "D203", + "D204", + "D213", + "D215", + "D400", + "D404", + "D406", + "D407", + "D408", + "D409", + "D413", +] +ignore = ["E501", "D107"] +extend-exclude = ["__pycache__", "*.egg_info"] +per-file-ignores = {"tests/*" = ["D100","D101","D102","D103","D104"]} + +[tool.ruff.mccabe] +max-complexity = 10 + +[tool.codespell] +skip = "build,lib,venv,icon.svg,.tox,.git,.mypy_cache,.ruff_cache,.coverage" + +[tool.pyright] +include = ["src/**.py"] + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..635423c5 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +ops ~= 2.4 diff --git a/src/charm.py b/src/charm.py new file mode 100755 index 00000000..c21dfb40 --- /dev/null +++ b/src/charm.py @@ -0,0 +1,103 @@ +#!/usr/bin/env python3 +# Copyright 2023 Ubuntu +# See LICENSE file for licensing details. +# +# Learn more at: https://juju.is/docs/sdk + +"""Charm the service. + +Refer to the following tutorial that will help you +develop a new k8s charm using the Operator Framework: + +https://juju.is/docs/sdk/create-a-minimal-kubernetes-charm +""" + +import logging + +import ops + +# Log messages can be retrieved using juju debug-log +logger = logging.getLogger(__name__) + +VALID_LOG_LEVELS = ["info", "debug", "warning", "error", "critical"] + + +class MongosOperatorCharm(ops.CharmBase): + """Charm the service.""" + + def __init__(self, *args): + super().__init__(*args) + self.framework.observe(self.on['httpbin'].pebble_ready, self._on_httpbin_pebble_ready) + self.framework.observe(self.on.config_changed, self._on_config_changed) + + def _on_httpbin_pebble_ready(self, event: ops.PebbleReadyEvent): + """Define and start a workload using the Pebble API. + + Change this example to suit your needs. You'll need to specify the right entrypoint and + environment configuration for your specific workload. + + Learn more about interacting with Pebble at at https://juju.is/docs/sdk/pebble. + """ + # Get a reference the container attribute on the PebbleReadyEvent + container = event.workload + # Add initial Pebble config layer using the Pebble API + container.add_layer("httpbin", self._pebble_layer, combine=True) + # Make Pebble reevaluate its plan, ensuring any services are started if enabled. + container.replan() + # Learn more about statuses in the SDK docs: + # https://juju.is/docs/sdk/constructs#heading--statuses + self.unit.status = ops.ActiveStatus() + + def _on_config_changed(self, event: ops.ConfigChangedEvent): + """Handle changed configuration. + + Change this example to suit your needs. If you don't need to handle config, you can remove + this method. + + Learn more about config at https://juju.is/docs/sdk/config + """ + # Fetch the new config value + log_level = self.model.config["log-level"].lower() + + # Do some validation of the configuration option + if log_level in VALID_LOG_LEVELS: + # The config is good, so update the configuration of the workload + container = self.unit.get_container("httpbin") + # Verify that we can connect to the Pebble API in the workload container + if container.can_connect(): + # Push an updated layer with the new config + container.add_layer("httpbin", self._pebble_layer, combine=True) + container.replan() + + logger.debug("Log level for gunicorn changed to '%s'", log_level) + self.unit.status = ops.ActiveStatus() + else: + # We were unable to connect to the Pebble API, so we defer this event + event.defer() + self.unit.status = ops.WaitingStatus("waiting for Pebble API") + else: + # In this case, the config option is bad, so block the charm and notify the operator. + self.unit.status = ops.BlockedStatus("invalid log level: '{log_level}'") + + @property + def _pebble_layer(self) -> ops.pebble.LayerDict: + """Return a dictionary representing a Pebble layer.""" + return { + "summary": "httpbin layer", + "description": "pebble config layer for httpbin", + "services": { + "httpbin": { + "override": "replace", + "summary": "httpbin", + "command": "gunicorn -b 0.0.0.0:80 httpbin:app -k gevent", + "startup": "enabled", + "environment": { + "GUNICORN_CMD_ARGS": f"--log-level {self.model.config['log-level']}" + }, + } + }, + } + + +if __name__ == "__main__": # pragma: nocover + ops.main(MongosOperatorCharm) # type: ignore diff --git a/tests/integration/test_charm.py b/tests/integration/test_charm.py new file mode 100644 index 00000000..71e4e4eb --- /dev/null +++ b/tests/integration/test_charm.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python3 +# Copyright 2023 Ubuntu +# See LICENSE file for licensing details. + +import asyncio +import logging +from pathlib import Path + +import pytest +import yaml +from pytest_operator.plugin import OpsTest + +logger = logging.getLogger(__name__) + +METADATA = yaml.safe_load(Path("./metadata.yaml").read_text()) +APP_NAME = METADATA["name"] + + +@pytest.mark.abort_on_fail +async def test_build_and_deploy(ops_test: OpsTest): + """Build the charm-under-test and deploy it together with related charms. + + Assert on the unit status before any relations/configurations take place. + """ + # Build and deploy charm from local source folder + charm = await ops_test.build_charm(".") + resources = {"httpbin-image": METADATA["resources"]["httpbin-image"]["upstream-source"]} + + # Deploy the charm and wait for active/idle status + await asyncio.gather( + ops_test.model.deploy(charm, resources=resources, application_name=APP_NAME), + ops_test.model.wait_for_idle( + apps=[APP_NAME], status="active", raise_on_blocked=True, timeout=1000 + ), + ) diff --git a/tests/unit/test_charm.py b/tests/unit/test_charm.py new file mode 100644 index 00000000..ae5d667c --- /dev/null +++ b/tests/unit/test_charm.py @@ -0,0 +1,68 @@ +# Copyright 2023 Ubuntu +# See LICENSE file for licensing details. +# +# Learn more about testing at: https://juju.is/docs/sdk/testing + +import unittest + +import ops +import ops.testing +from charm import MongosOperatorCharm + + +class TestCharm(unittest.TestCase): + def setUp(self): + self.harness = ops.testing.Harness(MongosOperatorCharm) + self.addCleanup(self.harness.cleanup) + self.harness.begin() + + def test_httpbin_pebble_ready(self): + # Expected plan after Pebble ready with default config + expected_plan = { + "services": { + "httpbin": { + "override": "replace", + "summary": "httpbin", + "command": "gunicorn -b 0.0.0.0:80 httpbin:app -k gevent", + "startup": "enabled", + "environment": {"GUNICORN_CMD_ARGS": "--log-level info"}, + } + }, + } + # Simulate the container coming up and emission of pebble-ready event + self.harness.container_pebble_ready("httpbin") + # Get the plan now we've run PebbleReady + updated_plan = self.harness.get_container_pebble_plan("httpbin").to_dict() + # Check we've got the plan we expected + self.assertEqual(expected_plan, updated_plan) + # Check the service was started + service = self.harness.model.unit.get_container("httpbin").get_service("httpbin") + self.assertTrue(service.is_running()) + # Ensure we set an ActiveStatus with no message + self.assertEqual(self.harness.model.unit.status, ops.ActiveStatus()) + + def test_config_changed_valid_can_connect(self): + # Ensure the simulated Pebble API is reachable + self.harness.set_can_connect("httpbin", True) + # Trigger a config-changed event with an updated value + self.harness.update_config({"log-level": "debug"}) + # Get the plan now we've run PebbleReady + updated_plan = self.harness.get_container_pebble_plan("httpbin").to_dict() + updated_env = updated_plan["services"]["httpbin"]["environment"] + # Check the config change was effective + self.assertEqual(updated_env, {"GUNICORN_CMD_ARGS": "--log-level debug"}) + self.assertEqual(self.harness.model.unit.status, ops.ActiveStatus()) + + def test_config_changed_valid_cannot_connect(self): + # Trigger a config-changed event with an updated value + self.harness.update_config({"log-level": "debug"}) + # Check the charm is in WaitingStatus + self.assertIsInstance(self.harness.model.unit.status, ops.WaitingStatus) + + def test_config_changed_invalid(self): + # Ensure the simulated Pebble API is reachable + self.harness.set_can_connect("httpbin", True) + # Trigger a config-changed event with an updated value + self.harness.update_config({"log-level": "foobar"}) + # Check the charm is in BlockedStatus + self.assertIsInstance(self.harness.model.unit.status, ops.BlockedStatus) diff --git a/tox.ini b/tox.ini new file mode 100644 index 00000000..e63c6c5e --- /dev/null +++ b/tox.ini @@ -0,0 +1,86 @@ +# Copyright 2023 Ubuntu +# See LICENSE file for licensing details. + +[tox] +no_package = True +skip_missing_interpreters = True +env_list = format, lint, static, unit +min_version = 4.0.0 + +[vars] +src_path = {tox_root}/src +tests_path = {tox_root}/tests +;lib_path = {tox_root}/lib/charms/operator_name_with_underscores +all_path = {[vars]src_path} {[vars]tests_path} + +[testenv] +set_env = + PYTHONPATH = {tox_root}/lib:{[vars]src_path} + PYTHONBREAKPOINT=pdb.set_trace + PY_COLORS=1 +pass_env = + PYTHONPATH + CHARM_BUILD_DIR + MODEL_SETTINGS + +[testenv:format] +description = Apply coding style standards to code +deps = + black + ruff +commands = + black {[vars]all_path} + ruff --fix {[vars]all_path} + +[testenv:lint] +description = Check code against coding style standards +deps = + black + ruff + codespell +commands = + # if this charm owns a lib, uncomment "lib_path" variable + # and uncomment the following line + # codespell {[vars]lib_path} + codespell {tox_root} + ruff {[vars]all_path} + black --check --diff {[vars]all_path} + +[testenv:unit] +description = Run unit tests +deps = + pytest + coverage[toml] + -r {tox_root}/requirements.txt +commands = + coverage run --source={[vars]src_path} \ + -m pytest \ + --tb native \ + -v \ + -s \ + {posargs} \ + {[vars]tests_path}/unit + coverage report + +[testenv:static] +description = Run static type checks +deps = + pyright + -r {tox_root}/requirements.txt +commands = + pyright {posargs} + +[testenv:integration] +description = Run integration tests +deps = + pytest + juju + pytest-operator + -r {tox_root}/requirements.txt +commands = + pytest -v \ + -s \ + --tb native \ + --log-cli-level=INFO \ + {posargs} \ + {[vars]tests_path}/integration From 0c691121e260ab14364d73d0a636239863f25a69 Mon Sep 17 00:00:00 2001 From: Mia Altieri Date: Mon, 20 Nov 2023 17:52:15 +0000 Subject: [PATCH 2/4] add workflows --- .github/ISSUE_TEMPLATE/bug_report.md | 47 +++++++++++++++++++++ .github/pull_request_template.md | 4 ++ .github/workflows/ci.yaml | 50 +++++++++++++++++++++++ .github/workflows/release.yaml | 49 ++++++++++++++++++++++ .github/workflows/sync_issue_to_jira.yaml | 21 ++++++++++ 5 files changed, 171 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/bug_report.md create mode 100644 .github/pull_request_template.md create mode 100644 .github/workflows/ci.yaml create mode 100644 .github/workflows/release.yaml create mode 100644 .github/workflows/sync_issue_to_jira.yaml diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..1a3f3789 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,47 @@ +--- +name: Bug report +about: File a bug report +labels: bug + +--- + + + +## Steps to reproduce + +1. + +## Expected behavior + + +## Actual behavior + + + +## Versions + + +Operating system: + + +Juju CLI: + + +Juju agent: + + +Charm revision: + + +LXD: + +## Log output + + +Juju debug log: + + + + +## Additional context + diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..05dc4e23 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,4 @@ +## Issue + + +## Solution diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml new file mode 100644 index 00000000..e7580396 --- /dev/null +++ b/.github/workflows/ci.yaml @@ -0,0 +1,50 @@ +# Copyright 2023 Canonical Ltd. +# See LICENSE file for licensing details. +name: Tests + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +on: + pull_request: + schedule: + - cron: "53 0 * * *" # Daily at 00:53 UTC + # Triggered on push to branch "main" by .github/workflows/release.yaml + workflow_call: + secrets: + CHARMHUB_TOKEN: + required: true + +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Install tox + # TODO: Consider replacing with custom image on self-hosted runner OR pinning version + run: python3 -m pip install tox + - name: Run linters + run: tox run -e lint + + lib-check: + name: Check libraries + runs-on: ubuntu-latest + timeout-minutes: 5 + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Check libs + uses: canonical/charming-actions/check-libraries@2.4.0 + with: + credentials: "${{ secrets.CHARMHUB_TOKEN }}" # FIXME: current token will expire in 2023-07-04 + github-token: "${{ secrets.GITHUB_TOKEN }}" + + build: + name: Build charms + uses: canonical/data-platform-workflows/.github/workflows/build_charms_with_cache.yaml@v5.1.2 diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml new file mode 100644 index 00000000..44a9eb72 --- /dev/null +++ b/.github/workflows/release.yaml @@ -0,0 +1,49 @@ +name: Release to 6/edge + +on: + push: + branches: + - 6/edge + +jobs: + ci-tests: + uses: ./.github/workflows/ci.yaml + secrets: + CHARMHUB_TOKEN: "${{ secrets.CHARMHUB_TOKEN }}" + + build: + name: Build charm + uses: canonical/data-platform-workflows/.github/workflows/build_charm_without_cache.yaml@v5.1.2 + with: + charmcraft-snap-channel: "latest/stable" + + release-charm: + name: Release charm + needs: + - ci-tests + - build + uses: canonical/data-platform-workflows/.github/workflows/release_charm.yaml@v5 + with: + channel: 6/edge + artifact-name: ${{ needs.build.outputs.artifact-name }} + secrets: + charmhub-token: ${{ secrets.CHARMHUB_TOKEN }} + permissions: + contents: write # Needed to create GitHub release + + release-libraries: + name: Release libraries + runs-on: ubuntu-latest + needs: + - ci-tests + - release-charm + steps: + - name: Checkout + uses: actions/checkout@v3 + with: + fetch-depth: 0 + - name: Release any bumped charm libs + uses: canonical/charming-actions/release-libraries@2.2.2 + with: + credentials: "${{ secrets.CHARMHUB_TOKEN }}" + github-token: "${{ secrets.GITHUB_TOKEN }}" diff --git a/.github/workflows/sync_issue_to_jira.yaml b/.github/workflows/sync_issue_to_jira.yaml new file mode 100644 index 00000000..4221f69e --- /dev/null +++ b/.github/workflows/sync_issue_to_jira.yaml @@ -0,0 +1,21 @@ +# Copyright 2023 Canonical Ltd. +# See LICENSE file for licensing details. +name: Sync issue to Jira + +on: + issues: + types: [opened, reopened, closed] + +jobs: + sync: + name: Sync GitHub issue to Jira + uses: canonical/data-platform-workflows/.github/workflows/sync_issue_to_jira.yaml@v2 + with: + jira-base-url: https://warthogs.atlassian.net + jira-project-key: DPE + jira-component-names: mongodb-vm + secrets: + jira-api-token: ${{ secrets.JIRA_API_TOKEN }} + jira-user-email: ${{ secrets.JIRA_USER_EMAIL }} + permissions: + issues: write # Needed to create GitHub issue comment From 5b0568c64431ae34b00122eafc0a5354e285f981 Mon Sep 17 00:00:00 2001 From: Mia Altieri Date: Mon, 20 Nov 2023 18:12:47 +0000 Subject: [PATCH 3/4] update files --- README.md | 25 +++------ metadata.yaml | 58 ++++++--------------- src/charm.py | 89 +-------------------------------- tests/integration/test_charm.py | 35 ------------- tests/unit/test_charm.py | 68 ------------------------- tox.ini | 30 +---------- 6 files changed, 26 insertions(+), 279 deletions(-) delete mode 100644 tests/integration/test_charm.py delete mode 100644 tests/unit/test_charm.py diff --git a/README.md b/README.md index 43de472a..5b633145 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,15 @@ - +[mongos](https://www.mongodb.com/docs/v6.0/reference/program/mongos/) is a popular NoSQL database application. It stores its data with JSON-like documents creating a flexible user experience with easy-to-use data aggregation for data analytics. In addition, it is a distributed database, so vertical and horizontal scaling come naturally. -# mongos-operator +Applications like mongos must be managed and operated in the production environment. This means that mongos proxy administrators and analysts who run workloads in various infrastructures should be able to automate tasks for repeatable operational work. Technologies such as software operators encapsulate the knowledge, wisdom and expertise of a real-world operations team and codify it into a computer program that helps to operate complex server applications like MongoDB and other databases. -Charmhub package name: operator-template -More information: https://charmhub.io/mongos-operator +Canonical has developed an open-source operator called Charmed Mongos, which make it easier to operate mongos. The Charmed mongos Virtual Machine (VM) operator deploys and operates mongos on physical, Virtual Machines (VM) and other wide range of cloud and cloud-like environments, including AWS, Azure, OpenStack and VMWare. -Describe your charm in one or two sentences. +Charmed monogs(VM Operator) is an enhanced, open source and fully-compatible drop-in replacement for the MongoDB Community Edition of mongos with advanced mongos enterprise features. It simplifies the deployment, scaling, design and management of mongos in production in a reliable way. -## Other resources +It acts as a [subordinate Charmed Operator](https://discourse.charmhub.io/t/subordinate-applications/1053) and is meant to act as a proxy to a sharded MongoDB cluster. To deploy a sharded MongoDB cluster please see our [Charmed solution for MongoDB](https://charmhub.io/mongodb) - -- [Read more](https://example.com) - -- [Contributing](CONTRIBUTING.md) - -- See the [Juju SDK documentation](https://juju.is/docs/sdk) for more information about developing and improving charms. diff --git a/metadata.yaml b/metadata.yaml index 80c2a848..c9db0afe 100644 --- a/metadata.yaml +++ b/metadata.yaml @@ -1,43 +1,17 @@ -# This file populates the Overview on Charmhub. -# See https://juju.is/docs/sdk/metadata-reference for a checklist and guidance. - -# The charm package name, no spaces (required) -# See https://juju.is/docs/sdk/naming#heading--naming-charms for guidance. -name: mongos-operator - -# The following metadata are human-readable and will be published prominently on Charmhub. - -# (Recommended) -display-name: Charm Template - -# (Required) -summary: A very short one-line summary of the charm. - +# Copyright 2023 Canonical Ltd. +# See LICENSE file for licensing details. +name: mongos +display-name: mongos description: | - A single sentence that says what the charm is, concisely and memorably. - - A paragraph of one to three short sentences, that describe what the charm does. - - A third paragraph that explains what need the charm meets. - - Finally, a paragraph that describes whom the charm is useful for. - -# The containers and resources metadata apply to Kubernetes charms only. -# Remove them if not required. - -# Your workload’s containers. -containers: - httpbin: - resource: httpbin-image - -# This field populates the Resources tab on Charmhub. -resources: - # An OCI image resource for each container listed above. - # You may remove this if your charm will run without a workload sidecar container. - httpbin-image: - type: oci-image - description: OCI image for httpbin - # The upstream-source field is ignored by Juju. It is included here as a reference - # so the integration testing suite knows which image to deploy during testing. This field - # is also used by the 'canonical/charming-actions' Github action for automated releasing. - upstream-source: kennethreitz/httpbin + mongos is a proxy used for sharded MongoDB clusters. This charm + deploys and operates mongos as a subordinate charm. +source: https://github.com/canonical/mongos-operator +issues: https://github.com/canonical/mongos-operator/issues +website: + - https://github.com/canonical/mongos-operator + - https://chat.charmhub.io/charmhub/channels/data-platform +summary: A mongos operator charm +series: + - jammy + +subordinate: true diff --git a/src/charm.py b/src/charm.py index c21dfb40..6c4e3496 100755 --- a/src/charm.py +++ b/src/charm.py @@ -1,103 +1,18 @@ #!/usr/bin/env python3 -# Copyright 2023 Ubuntu +"""Charm code for `mongos` daemon.""" +# Copyright 2023 Canonical Ltd. # See LICENSE file for licensing details. -# -# Learn more at: https://juju.is/docs/sdk -"""Charm the service. - -Refer to the following tutorial that will help you -develop a new k8s charm using the Operator Framework: - -https://juju.is/docs/sdk/create-a-minimal-kubernetes-charm -""" import logging import ops -# Log messages can be retrieved using juju debug-log logger = logging.getLogger(__name__) -VALID_LOG_LEVELS = ["info", "debug", "warning", "error", "critical"] - class MongosOperatorCharm(ops.CharmBase): """Charm the service.""" def __init__(self, *args): super().__init__(*args) - self.framework.observe(self.on['httpbin'].pebble_ready, self._on_httpbin_pebble_ready) - self.framework.observe(self.on.config_changed, self._on_config_changed) - - def _on_httpbin_pebble_ready(self, event: ops.PebbleReadyEvent): - """Define and start a workload using the Pebble API. - - Change this example to suit your needs. You'll need to specify the right entrypoint and - environment configuration for your specific workload. - - Learn more about interacting with Pebble at at https://juju.is/docs/sdk/pebble. - """ - # Get a reference the container attribute on the PebbleReadyEvent - container = event.workload - # Add initial Pebble config layer using the Pebble API - container.add_layer("httpbin", self._pebble_layer, combine=True) - # Make Pebble reevaluate its plan, ensuring any services are started if enabled. - container.replan() - # Learn more about statuses in the SDK docs: - # https://juju.is/docs/sdk/constructs#heading--statuses - self.unit.status = ops.ActiveStatus() - - def _on_config_changed(self, event: ops.ConfigChangedEvent): - """Handle changed configuration. - - Change this example to suit your needs. If you don't need to handle config, you can remove - this method. - - Learn more about config at https://juju.is/docs/sdk/config - """ - # Fetch the new config value - log_level = self.model.config["log-level"].lower() - - # Do some validation of the configuration option - if log_level in VALID_LOG_LEVELS: - # The config is good, so update the configuration of the workload - container = self.unit.get_container("httpbin") - # Verify that we can connect to the Pebble API in the workload container - if container.can_connect(): - # Push an updated layer with the new config - container.add_layer("httpbin", self._pebble_layer, combine=True) - container.replan() - - logger.debug("Log level for gunicorn changed to '%s'", log_level) - self.unit.status = ops.ActiveStatus() - else: - # We were unable to connect to the Pebble API, so we defer this event - event.defer() - self.unit.status = ops.WaitingStatus("waiting for Pebble API") - else: - # In this case, the config option is bad, so block the charm and notify the operator. - self.unit.status = ops.BlockedStatus("invalid log level: '{log_level}'") - - @property - def _pebble_layer(self) -> ops.pebble.LayerDict: - """Return a dictionary representing a Pebble layer.""" - return { - "summary": "httpbin layer", - "description": "pebble config layer for httpbin", - "services": { - "httpbin": { - "override": "replace", - "summary": "httpbin", - "command": "gunicorn -b 0.0.0.0:80 httpbin:app -k gevent", - "startup": "enabled", - "environment": { - "GUNICORN_CMD_ARGS": f"--log-level {self.model.config['log-level']}" - }, - } - }, - } - - -if __name__ == "__main__": # pragma: nocover - ops.main(MongosOperatorCharm) # type: ignore diff --git a/tests/integration/test_charm.py b/tests/integration/test_charm.py deleted file mode 100644 index 71e4e4eb..00000000 --- a/tests/integration/test_charm.py +++ /dev/null @@ -1,35 +0,0 @@ -#!/usr/bin/env python3 -# Copyright 2023 Ubuntu -# See LICENSE file for licensing details. - -import asyncio -import logging -from pathlib import Path - -import pytest -import yaml -from pytest_operator.plugin import OpsTest - -logger = logging.getLogger(__name__) - -METADATA = yaml.safe_load(Path("./metadata.yaml").read_text()) -APP_NAME = METADATA["name"] - - -@pytest.mark.abort_on_fail -async def test_build_and_deploy(ops_test: OpsTest): - """Build the charm-under-test and deploy it together with related charms. - - Assert on the unit status before any relations/configurations take place. - """ - # Build and deploy charm from local source folder - charm = await ops_test.build_charm(".") - resources = {"httpbin-image": METADATA["resources"]["httpbin-image"]["upstream-source"]} - - # Deploy the charm and wait for active/idle status - await asyncio.gather( - ops_test.model.deploy(charm, resources=resources, application_name=APP_NAME), - ops_test.model.wait_for_idle( - apps=[APP_NAME], status="active", raise_on_blocked=True, timeout=1000 - ), - ) diff --git a/tests/unit/test_charm.py b/tests/unit/test_charm.py deleted file mode 100644 index ae5d667c..00000000 --- a/tests/unit/test_charm.py +++ /dev/null @@ -1,68 +0,0 @@ -# Copyright 2023 Ubuntu -# See LICENSE file for licensing details. -# -# Learn more about testing at: https://juju.is/docs/sdk/testing - -import unittest - -import ops -import ops.testing -from charm import MongosOperatorCharm - - -class TestCharm(unittest.TestCase): - def setUp(self): - self.harness = ops.testing.Harness(MongosOperatorCharm) - self.addCleanup(self.harness.cleanup) - self.harness.begin() - - def test_httpbin_pebble_ready(self): - # Expected plan after Pebble ready with default config - expected_plan = { - "services": { - "httpbin": { - "override": "replace", - "summary": "httpbin", - "command": "gunicorn -b 0.0.0.0:80 httpbin:app -k gevent", - "startup": "enabled", - "environment": {"GUNICORN_CMD_ARGS": "--log-level info"}, - } - }, - } - # Simulate the container coming up and emission of pebble-ready event - self.harness.container_pebble_ready("httpbin") - # Get the plan now we've run PebbleReady - updated_plan = self.harness.get_container_pebble_plan("httpbin").to_dict() - # Check we've got the plan we expected - self.assertEqual(expected_plan, updated_plan) - # Check the service was started - service = self.harness.model.unit.get_container("httpbin").get_service("httpbin") - self.assertTrue(service.is_running()) - # Ensure we set an ActiveStatus with no message - self.assertEqual(self.harness.model.unit.status, ops.ActiveStatus()) - - def test_config_changed_valid_can_connect(self): - # Ensure the simulated Pebble API is reachable - self.harness.set_can_connect("httpbin", True) - # Trigger a config-changed event with an updated value - self.harness.update_config({"log-level": "debug"}) - # Get the plan now we've run PebbleReady - updated_plan = self.harness.get_container_pebble_plan("httpbin").to_dict() - updated_env = updated_plan["services"]["httpbin"]["environment"] - # Check the config change was effective - self.assertEqual(updated_env, {"GUNICORN_CMD_ARGS": "--log-level debug"}) - self.assertEqual(self.harness.model.unit.status, ops.ActiveStatus()) - - def test_config_changed_valid_cannot_connect(self): - # Trigger a config-changed event with an updated value - self.harness.update_config({"log-level": "debug"}) - # Check the charm is in WaitingStatus - self.assertIsInstance(self.harness.model.unit.status, ops.WaitingStatus) - - def test_config_changed_invalid(self): - # Ensure the simulated Pebble API is reachable - self.harness.set_can_connect("httpbin", True) - # Trigger a config-changed event with an updated value - self.harness.update_config({"log-level": "foobar"}) - # Check the charm is in BlockedStatus - self.assertIsInstance(self.harness.model.unit.status, ops.BlockedStatus) diff --git a/tox.ini b/tox.ini index e63c6c5e..4272891c 100644 --- a/tox.ini +++ b/tox.ini @@ -46,21 +46,6 @@ commands = ruff {[vars]all_path} black --check --diff {[vars]all_path} -[testenv:unit] -description = Run unit tests -deps = - pytest - coverage[toml] - -r {tox_root}/requirements.txt -commands = - coverage run --source={[vars]src_path} \ - -m pytest \ - --tb native \ - -v \ - -s \ - {posargs} \ - {[vars]tests_path}/unit - coverage report [testenv:static] description = Run static type checks @@ -70,17 +55,4 @@ deps = commands = pyright {posargs} -[testenv:integration] -description = Run integration tests -deps = - pytest - juju - pytest-operator - -r {tox_root}/requirements.txt -commands = - pytest -v \ - -s \ - --tb native \ - --log-cli-level=INFO \ - {posargs} \ - {[vars]tests_path}/integration + From 9c75db76da7899a9b168bdab02185f71f52c8251 Mon Sep 17 00:00:00 2001 From: Mia Altieri Date: Mon, 20 Nov 2023 18:53:12 +0000 Subject: [PATCH 4/4] remove config --- config.yaml | 14 -------------- 1 file changed, 14 deletions(-) delete mode 100644 config.yaml diff --git a/config.yaml b/config.yaml deleted file mode 100644 index 26b5b563..00000000 --- a/config.yaml +++ /dev/null @@ -1,14 +0,0 @@ -# This file defines charm config options, and populates the Configure tab on Charmhub. -# If your charm does not require configuration options, delete this file entirely. -# -# See https://juju.is/docs/config for guidance. - -options: - # An example config option to customise the log level of the workload - log-level: - description: | - Configures the log level of gunicorn. - - Acceptable values are: "info", "debug", "warning", "error" and "critical" - default: "info" - type: string