From 9342f56111b78a50843e144cdd9ae5e1df7cf4b2 Mon Sep 17 00:00:00 2001 From: Hemanth Nakkina Date: Fri, 30 Aug 2024 09:45:24 +0530 Subject: [PATCH] Add watcher-k8s charm Add charm for watcher-k8s Add unit tests and update integration tests Add zuul related changes to build and publish watcher-k8s charm Change-Id: I8295780743d9db976df753986884d1e031454cb3 --- charms/watcher-k8s/.sunbeam-build.yaml | 20 ++ charms/watcher-k8s/CONTRIBUTING.md | 31 ++ charms/watcher-k8s/LICENSE | 202 +++++++++++++ charms/watcher-k8s/README.md | 68 +++++ charms/watcher-k8s/charmcraft.yaml | 130 +++++++++ charms/watcher-k8s/rebuild | 1 + charms/watcher-k8s/requirements.txt | 7 + charms/watcher-k8s/src/charm.py | 270 ++++++++++++++++++ .../watcher-k8s/src/templates/watcher.conf.j2 | 73 +++++ .../src/templates/wsgi-watcher-api.conf.j2 | 27 ++ charms/watcher-k8s/tests/unit/__init__.py | 15 + charms/watcher-k8s/tests/unit/test_charm.py | 125 ++++++++ roles/charm-publish/defaults/main.yaml | 1 + tests/ceph/smoke.yaml.j2 | 29 +- tests/ceph/tests.yaml | 3 + zuul.d/jobs.yaml | 29 ++ zuul.d/project-templates.yaml | 6 + zuul.d/secrets.yaml | 142 ++++----- zuul.d/zuul.yaml | 1 + 19 files changed, 1107 insertions(+), 73 deletions(-) create mode 100644 charms/watcher-k8s/.sunbeam-build.yaml create mode 100644 charms/watcher-k8s/CONTRIBUTING.md create mode 100644 charms/watcher-k8s/LICENSE create mode 100644 charms/watcher-k8s/README.md create mode 100644 charms/watcher-k8s/charmcraft.yaml create mode 100644 charms/watcher-k8s/rebuild create mode 100644 charms/watcher-k8s/requirements.txt create mode 100755 charms/watcher-k8s/src/charm.py create mode 100644 charms/watcher-k8s/src/templates/watcher.conf.j2 create mode 100644 charms/watcher-k8s/src/templates/wsgi-watcher-api.conf.j2 create mode 100644 charms/watcher-k8s/tests/unit/__init__.py create mode 100644 charms/watcher-k8s/tests/unit/test_charm.py diff --git a/charms/watcher-k8s/.sunbeam-build.yaml b/charms/watcher-k8s/.sunbeam-build.yaml new file mode 100644 index 00000000..43bf4fd5 --- /dev/null +++ b/charms/watcher-k8s/.sunbeam-build.yaml @@ -0,0 +1,20 @@ +external-libraries: + - charms.data_platform_libs.v0.data_interfaces + - charms.rabbitmq_k8s.v0.rabbitmq + - charms.traefik_k8s.v2.ingress + - charms.certificate_transfer_interface.v0.certificate_transfer + - charms.loki_k8s.v1.loki_push_api + - charms.tempo_k8s.v2.tracing + - charms.tempo_k8s.v1.charm_tracing +internal-libraries: + - charms.keystone_k8s.v1.identity_service + - charms.gnocchi_k8s.v0.gnocchi_service +templates: + - parts/section-database + - parts/database-connection + - parts/database-connection-settings + - parts/section-identity + - parts/identity-data + - parts/section-oslo-messaging-rabbit + - parts/section-oslo-notifications + - ca-bundle.pem.j2 diff --git a/charms/watcher-k8s/CONTRIBUTING.md b/charms/watcher-k8s/CONTRIBUTING.md new file mode 100644 index 00000000..ef98f157 --- /dev/null +++ b/charms/watcher-k8s/CONTRIBUTING.md @@ -0,0 +1,31 @@ +# 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 fmt # update your code according to linting rules +tox run -e pep8 # code style +tox run -e linters # static type checking +tox run -e py3 # unit tests +tox # runs 'format', 'lint', 'static', and 'unit' environments +``` + +## Build the charm + +Build the charm in this git repository using: + +```shell +tox run -e build watcher-k8s +``` \ No newline at end of file diff --git a/charms/watcher-k8s/LICENSE b/charms/watcher-k8s/LICENSE new file mode 100644 index 00000000..af93a234 --- /dev/null +++ b/charms/watcher-k8s/LICENSE @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2023 Chi Wai CHAN + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/charms/watcher-k8s/README.md b/charms/watcher-k8s/README.md new file mode 100644 index 00000000..106ac28c --- /dev/null +++ b/charms/watcher-k8s/README.md @@ -0,0 +1,68 @@ +# watcher-k8s + +## Description + +watcher-k8s is an operator to manage the watcher services watcher api, +watcher decision engine and watcher applier on a Kubernetes based environment. + +## Usage + +### Deployment + +watcher-k8s is deployed using below command: + + juju deploy watcher-k8s watcher --trust + +Now connect the watcher operator to existing database, keystone identity, +and rabbitmq operators: + + juju relate mysql:database watcher:database + juju relate keystone:identity-service watcher:identity-service + juju relate rabbitmq:amqp watcher:amqp + +### Configuration + +This section covers common and/or important configuration options. See file +`config.yaml` for the full list of options, along with their descriptions and +default values. See the [Juju documentation][juju-docs-config-apps] for details +on configuring applications. + +### Actions + +This section covers Juju [actions][juju-docs-actions] supported by the charm. +Actions allow specific operations to be performed on a per-unit basis. To +display action descriptions run `juju actions watcher`. If the charm is not +deployed then see file `actions.yaml`. + +## Relations + +watcher-k8s requires the following relations: + +`database`: To connect to MySQL +`identity-service`: To register endpoints in Keystone +`ingress-internal`: To expose service on underlying internal network +`ingress-public`: To expose service on public network +`amqp`: To connect to Rabbitmq + +## OCI Images + +The charm by default uses following images: + + ghcr.io/canonical/watcher-consolidated:2024.1 + +## Contributing + +Please see the [Juju SDK docs](https://juju.is/docs/sdk) for guidelines +on enhancements to this charm following best practice guidelines, and +[CONTRIBUTING.md](contributors-guide) for developer guidance. + +## Bugs + +Please report bugs on [Launchpad][lp-bugs-charm-watcher-k8s]. + + + +[contributors-guide]: https://opendev.org/openstack/charm-watcher-k8s/src/branch/main/CONTRIBUTING.md +[juju-docs-actions]: https://jaas.ai/docs/actions +[juju-docs-config-apps]: https://juju.is/docs/configuring-applications +[lp-bugs-charm-watcher-k8s]: https://bugs.launchpad.net/sunbeam-charms/+filebug \ No newline at end of file diff --git a/charms/watcher-k8s/charmcraft.yaml b/charms/watcher-k8s/charmcraft.yaml new file mode 100644 index 00000000..e0ff27e5 --- /dev/null +++ b/charms/watcher-k8s/charmcraft.yaml @@ -0,0 +1,130 @@ +type: "charm" +bases: + - build-on: + - name: "ubuntu" + channel: "22.04" + run-on: + - name: "ubuntu" + channel: "22.04" +parts: + update-certificates: + plugin: nil + override-build: | + apt update + apt install -y ca-certificates + update-ca-certificates + + charm: + after: [update-certificates] + build-packages: + - git + - libffi-dev + - libssl-dev + - rustc + - cargo + - pkg-config + charm-binary-python-packages: + - cryptography + - jsonschema + - pydantic + - jinja2 + +name: watcher-k8s +title: OpenStack watcher service +summary: Resource Optimization service for OpenStack +description: | + watcher-k8s is a charm for OpenStack Watcher service which + provides a flexible and scalable resource optimization + for multi-tenant OpenStack-based clouds. + + The charm handles instantiation, scaling, configuration and + Day 2 operations for OpenStack Watcher services. + +links: + source: https://opendev.org/openstack/sunbeam-charms + issues: https://bugs.launchpad.net/sunbeam-charms + +assumes: +- k8s-api +- juju >= 3.1 + +containers: + watcher-api: + resource: watcher-image + watcher-applier: + resource: watcher-image + watcher-decision-engine: + resource: watcher-image + +resources: + watcher-image: + description: OCI image for OpenStack Watcher services + type: oci-image + upstream-source: ghcr.io/canonical/watcher-consolidated:2024.1 + +requires: + amqp: + interface: rabbitmq + limit: 1 + database: + interface: mysql_client + limit: 1 + gnocchi-db: + interface: gnocchi + optional: true + identity-service: + interface: keystone + limit: 1 + ingress-internal: + interface: ingress + limit: 1 + optional: true + ingress-public: + interface: ingress + limit: 1 + logging: + interface: loki_push_api + limit: 1 + optional: true + receive-ca-cert: + interface: certificate_transfer + limit: 1 + optional: true + tracing: + interface: tracing + limit: 1 + optional: true + +peers: + peers: + interface: watcher-peer + +config: + options: + collector-plugins: + default: compute + description: | + A comma separated list of cluster data model plugin names. + . + Available collector-plugins are: compute and storage. + type: string + debug: + default: false + description: Enable debug logging. + type: boolean + enable-telemetry-notifications: + default: false + description: Enable notifications to send to telemetry. + type: boolean + maas-api-key: + description: MAAS API authentication key + type: string + maas-url: + description: MAAS URL to connect + type: string + region: + default: RegionOne + description: Name of the OpenStack region + type: string + +actions: {} diff --git a/charms/watcher-k8s/rebuild b/charms/watcher-k8s/rebuild new file mode 100644 index 00000000..35f938c7 --- /dev/null +++ b/charms/watcher-k8s/rebuild @@ -0,0 +1 @@ +b47d3d6a-641c-11ef-ae1f-278a03cb283b \ No newline at end of file diff --git a/charms/watcher-k8s/requirements.txt b/charms/watcher-k8s/requirements.txt new file mode 100644 index 00000000..d366d3cd --- /dev/null +++ b/charms/watcher-k8s/requirements.txt @@ -0,0 +1,7 @@ +ops +jinja2 +lightkube +pydantic<2.0 + +# From ops_sunbeam +tenacity \ No newline at end of file diff --git a/charms/watcher-k8s/src/charm.py b/charms/watcher-k8s/src/charm.py new file mode 100755 index 00000000..c802d4d2 --- /dev/null +++ b/charms/watcher-k8s/src/charm.py @@ -0,0 +1,270 @@ +#!/usr/bin/env python3 +# Copyright 2024 Canonical Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +"""Watcher Operator Charm. + +This charm provide watcher services as part of an OpenStack deployment +""" + +import logging + +import ops +import ops_sunbeam.charm as sunbeam_charm +import ops_sunbeam.container_handlers as sunbeam_chandlers +import ops_sunbeam.core as sunbeam_core +import ops_sunbeam.relation_handlers as sunbeam_rhandlers +import ops_sunbeam.tracing as sunbeam_tracing + +logger = logging.getLogger(__name__) +WATCHER_API_CONTAINER = "watcher-api" +WATCHER_DECISION_ENGINE_CONTAINER = "watcher-decision-engine" +WATCHER_APPLIER_CONTAINER = "watcher-applier" + + +@sunbeam_tracing.trace_type +class WatcherDecisionEnginePebbleHandler( + sunbeam_chandlers.ServicePebbleHandler +): + """Pebble handler for Watcher decision engine.""" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.enable_service_check = True + + def get_layer(self) -> dict: + """Watcher decision engine service layer. + + :returns: pebble layer configuration for watcher decision engine service + :rtype: dict + """ + return { + "summary": "watcher decision engine layer", + "description": "pebble configuration for watcher services", + "services": { + "watcher-decision-engine": { + "override": "replace", + "summary": "Watcher decision engine", + "command": "watcher-decision-engine", + "user": self.charm.service_user, + "group": self.charm.service_group, + } + }, + } + + @property + def service_ready(self) -> bool: + """Determine whether the service the container provides is running.""" + if self.enable_service_check: + logger.debug("Service checks enabled for watcher decision engine") + return super().service_ready + else: + logger.debug("Service checks disabled for watcher decision engine") + return self.pebble_ready + + +@sunbeam_tracing.trace_type +class WatcherApplierPebbleHandler(sunbeam_chandlers.ServicePebbleHandler): + """Pebble handler for Watcher applier.""" + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.enable_service_check = True + + def get_layer(self) -> dict: + """Watcher applier service layer. + + :returns: pebble layer configuration for watcher applier service + :rtype: dict + """ + return { + "summary": "watcher applier layer", + "description": "pebble configuration for watcher services", + "services": { + "watcher-applier": { + "override": "replace", + "summary": "Watcher applier", + "command": "watcher-applier", + "user": self.charm.service_user, + "group": self.charm.service_group, + } + }, + } + + @property + def service_ready(self) -> bool: + """Determine whether the service the container provides is running.""" + if self.enable_service_check: + logger.debug("Service checks enabled for watcher applier") + return super().service_ready + else: + logger.debug("Service checks disabled for watcher applier") + return self.pebble_ready + + +class WatcherOperatorCharm(sunbeam_charm.OSBaseOperatorAPICharm): + """Charm the service.""" + + service_name = "watcher-api" + wsgi_admin_script = "/usr/bin/watcher-api-wsgi" + wsgi_public_script = "/usr/bin/watcher-api-wsgi" + mandatory_relations = {"database", "amqp", "identity-service"} + + db_sync_cmds = [ + [ + "sudo", + "-u", + "watcher", + "watcher-db-manage", + "upgrade", + ] + ] + + @property + def service_conf(self) -> str: + """Service default configuration file.""" + return "/etc/watcher/watcher.conf" + + @property + def service_user(self) -> str: + """Service user file and directory ownership.""" + return "watcher" + + @property + def service_group(self) -> str: + """Service group file and directory ownership.""" + return "watcher" + + @property + def service_endpoints(self): + """Service endpoints configuration.""" + return [ + { + "service_name": "watcher", + "type": "infra-optim", + "description": "Infrastructure Optimization", + "internal_url": f"{self.internal_url}", + "public_url": f"{self.public_url}", + "admin_url": f"{self.admin_url}", + } + ] + + @property + def default_public_ingress_port(self): + """Default port.""" + return 9322 + + @property + def container_configs(self) -> list[sunbeam_core.ContainerConfigFile]: + """Container config files for the service.""" + _cconfigs = super().container_configs + _cconfigs.extend( + [ + sunbeam_core.ContainerConfigFile( + self.service_conf, + self.service_user, + self.service_group, + 0o640, + ), + sunbeam_core.ContainerConfigFile( + "/usr/local/share/ca-certificates/ca-bundle.pem", + "root", + self.service_group, + 0o640, + ), + ] + ) + + return _cconfigs + + def get_pebble_handlers( + self, + ) -> list[sunbeam_chandlers.ServicePebbleHandler]: + """Pebble handlers for operator.""" + pebble_handlers = super().get_pebble_handlers() + pebble_handlers.extend( + [ + WatcherDecisionEnginePebbleHandler( + self, + WATCHER_DECISION_ENGINE_CONTAINER, + "watcher-decision-engine", + self.container_configs, + self.template_dir, + self.configure_charm, + ), + WatcherApplierPebbleHandler( + self, + WATCHER_APPLIER_CONTAINER, + "watcher-applier", + self.container_configs, + self.template_dir, + self.configure_charm, + ), + ] + ) + return pebble_handlers + + def get_relation_handlers( + self, handlers: list[sunbeam_rhandlers.RelationHandler] = None + ) -> list[sunbeam_rhandlers.RelationHandler]: + """Relation handlers for the service.""" + handlers = handlers or [] + if self.can_add_handler("gnocchi-db", handlers): + self.gnocchi_svc = sunbeam_rhandlers.GnocchiServiceRequiresHandler( + self, + "gnocchi-db", + self.configure_charm, + "gnocchi-db" in self.mandatory_relations, + ) + handlers.append(self.gnocchi_svc) + + return super().get_relation_handlers(handlers) + + def configure_charm(self, event: ops.framework.EventBase) -> None: + """Callback handler for watcher operator configuration.""" + # Do not run service check for watcher decision engine and watcher + # applier as it is broken until db migrations have run. + decision_engine_handler = self.get_named_pebble_handler( + WATCHER_DECISION_ENGINE_CONTAINER + ) + decision_engine_handler.enable_service_check = False + applier_handler = self.get_named_pebble_handler( + WATCHER_APPLIER_CONTAINER + ) + applier_handler.enable_service_check = False + + super().configure_charm(event) + if decision_engine_handler.pebble_ready: + logger.debug( + "Starting watcher decision engine service, pebble ready" + ) + # Restart watcher-decision-engine service + decision_engine_handler.start_all() + decision_engine_handler.enable_service_check = True + else: + logger.debug( + "Not starting watcher decision engine service, pebble not ready" + ) + if applier_handler.pebble_ready: + logger.debug("Starting watcher applier service, pebble ready") + # Restart watcher-applier service + applier_handler.start_all() + applier_handler.enable_service_check = True + else: + logger.debug( + "Not starting watcher applier service, pebble not ready" + ) + + +if __name__ == "__main__": # pragma: nocover + ops.main(WatcherOperatorCharm) # type: ignore diff --git a/charms/watcher-k8s/src/templates/watcher.conf.j2 b/charms/watcher-k8s/src/templates/watcher.conf.j2 new file mode 100644 index 00000000..9a55062f --- /dev/null +++ b/charms/watcher-k8s/src/templates/watcher.conf.j2 @@ -0,0 +1,73 @@ +[DEFAULT] +debug = {{ options.debug }} +transport_url = {{ amqp.transport_url }} + +{% include "parts/section-database" %} + +{% include "parts/section-identity" %} + +[watcher_clients_auth] +{% include "parts/identity-data" %} + + +{% include "parts/section-oslo-notifications" %} + +{% include "parts/section-oslo-messaging-rabbit" %} + +[api] +# The listen IP address for the watcher API server (host address value) +host = 0.0.0.0 +workers = 4 + +[watcher_applier] +# Number of workers for applier, default value is 1. (integer value) +# Minimum value: 1 +workers = 4 + +[watcher_decision_engine] +# metric_map_path = /etc/watcher/metric_map.yaml + +[watcher_datasources] +# ceilometer is deprecated, monasca is not supported in sunbeam-charms +# grafana works only with InfluxDB +# Available datasources are gnocchi +datasources = gnocchi + +{% if options.collector_plugins -%} +[collector] +# maas is not supported as baremetal collector as watcher strategy +# implementations are not using baremetal collectors +# https://opendev.org/openstack/watcher/commit/c95ce4ec17aa7844f64e97d67cf66c017d656c47 +# Not adding baremetal as ironic is not supported in sunbeam-charms +collector_plugins = {{ options.collector_plugins }} +{% endif -%} + +[cinder_client] +region_name = {{ options.region }} + +[glance_client] +region_name = {{ options.region }} + +[keystone_client] +region_name = {{ options.region }} + +[neutron_client] +region_name = {{ options.region }} + +[nova_client] +region_name = {{ options.region }} + +[placement_client] +region_name = {{ options.region }} + +[gnocchi_client] +region_name = {{ options.region }} + +[ironic_client] +region_name = {{ options.region }} + +{% if options.maas_url and options.maas_api_key -%} +[maas_client] +url = {{ options.maas_url }} +api_key = {{ options.maas_api_key }} +{% endif -%} \ No newline at end of file diff --git a/charms/watcher-k8s/src/templates/wsgi-watcher-api.conf.j2 b/charms/watcher-k8s/src/templates/wsgi-watcher-api.conf.j2 new file mode 100644 index 00000000..066bd247 --- /dev/null +++ b/charms/watcher-k8s/src/templates/wsgi-watcher-api.conf.j2 @@ -0,0 +1,27 @@ +Listen {{ wsgi_config.public_port }} + + WSGIDaemonProcess {{ wsgi_config.name }} processes=4 threads=1 user={{ wsgi_config.user }} group={{ wsgi_config.group }} \ + display-name=%{GROUP} + WSGIProcessGroup {{ wsgi_config.name }} + {% if ingress_public and ingress_public.ingress_path -%} + WSGIScriptAlias {{ ingress_public.ingress_path }} {{ wsgi_config.wsgi_public_script }} + {% endif -%} + WSGIScriptAlias / {{ wsgi_config.wsgi_public_script }} + WSGIApplicationGroup %{GLOBAL} + WSGIPassAuthorization On + = 2.4> + ErrorLogFormat "%{cu}t %M" + + ErrorLog {{ wsgi_config.error_log }} + CustomLog {{ wsgi_config.custom_log }} combined + + + = 2.4> + Require all granted + + + Order allow,deny + Allow from all + + + \ No newline at end of file diff --git a/charms/watcher-k8s/tests/unit/__init__.py b/charms/watcher-k8s/tests/unit/__init__.py new file mode 100644 index 00000000..199c1af8 --- /dev/null +++ b/charms/watcher-k8s/tests/unit/__init__.py @@ -0,0 +1,15 @@ +# Copyright 2024 Canonical Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Unit tests for watcher operator.""" diff --git a/charms/watcher-k8s/tests/unit/test_charm.py b/charms/watcher-k8s/tests/unit/test_charm.py new file mode 100644 index 00000000..51f15674 --- /dev/null +++ b/charms/watcher-k8s/tests/unit/test_charm.py @@ -0,0 +1,125 @@ +#!/usr/bin/env python3 + +# Copyright 2024 Canonical Ltd. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Tests for watcher charm.""" + +from pathlib import ( + Path, +) + +import charm +import ops_sunbeam.test_utils as test_utils +import yaml + +charmcraft = (Path(__file__).parents[2] / "charmcraft.yaml").read_text() +config = yaml.dump(yaml.safe_load(charmcraft)["config"]) +actions = yaml.dump(yaml.safe_load(charmcraft)["actions"]) + + +class _WatcherOperatorCharm(charm.WatcherOperatorCharm): + def __init__(self, framework): + self.seen_events = [] + super().__init__(framework) + + def _log_event(self, event): + self.seen_events.append(type(event).__name__) + + def configure_charm(self, event): + super().configure_charm(event) + self._log_event(event) + + @property + def public_ingress_address(self): + return "watcher.juju" + + +class TestWatcherOperatorCharm(test_utils.CharmTestCase): + """Class for testing watcher charm.""" + + PATCHES = [] + + def setUp(self): + """Setup Watcher tests.""" + super().setUp(charm, self.PATCHES) + self.harness = test_utils.get_harness( + _WatcherOperatorCharm, + container_calls=self.container_calls, + charm_metadata=charmcraft, + charm_config=config, + charm_actions=actions, + ) + + # clean up events that were dynamically defined, + # otherwise we get issues because they'll be redefined, + # which is not allowed. + from charms.data_platform_libs.v0.data_interfaces import ( + DatabaseRequiresEvents, + ) + + for attr in ( + "database_database_created", + "database_endpoints_changed", + "database_read_only_endpoints_changed", + ): + try: + delattr(DatabaseRequiresEvents, attr) + except AttributeError: + pass + + self.addCleanup(self.harness.cleanup) + + def test_pebble_ready_handler(self): + """Test Pebble ready event is captured.""" + self.harness.begin() + self.assertEqual(self.harness.charm.seen_events, []) + test_utils.set_all_pebbles_ready(self.harness) + self.assertEqual( + self.harness.charm.seen_events, + ["PebbleReadyEvent", "PebbleReadyEvent", "PebbleReadyEvent"], + ) + + def test_all_relations(self): + """Test all the charms relations.""" + self.harness.begin_with_initial_hooks() + self.harness.set_leader() + test_utils.set_all_pebbles_ready(self.harness) + test_utils.add_api_relations(self.harness) + test_utils.add_complete_ingress_relation(self.harness) + # Add gnochi-db optional relation + self.harness.add_relation( + "gnocchi-db", "gnocchi", app_data={"ready": "true"} + ) + + setup_cmds = [ + [ + "sudo", + "-u", + "watcher", + "watcher-db-manage", + "upgrade", + ], + ] + for cmd in setup_cmds: + self.assertIn(cmd, self.container_calls.execute["watcher-api"]) + + for f in [ + "/etc/apache2/sites-available/wsgi-watcher-api.conf", + "/etc/watcher/watcher.conf", + ]: + self.check_file("watcher-api", f) + + self.check_file("watcher-decision-engine", "/etc/watcher/watcher.conf") + self.check_file("watcher-applier", "/etc/watcher/watcher.conf") diff --git a/roles/charm-publish/defaults/main.yaml b/roles/charm-publish/defaults/main.yaml index 48c7fd89..5d3ce103 100644 --- a/roles/charm-publish/defaults/main.yaml +++ b/roles/charm-publish/defaults/main.yaml @@ -23,3 +23,4 @@ publish_channels: openstack-hypervisor: latest/edge sunbeam-machine: latest/edge sunbeam-clusterd: latest/edge + watcher-k8s: latest/edge diff --git a/tests/ceph/smoke.yaml.j2 b/tests/ceph/smoke.yaml.j2 index adfff2df..e364c4f2 100644 --- a/tests/ceph/smoke.yaml.j2 +++ b/tests/ceph/smoke.yaml.j2 @@ -77,8 +77,8 @@ applications: scale: 1 trust: true resources: - gnocchi-api-image: ghcr.io/canonical/gnocchi-consolidated:2023.1 - gnocchi-metricd-image: ghcr.io/canonical/gnocchi-consolidated:2023.1 + gnocchi-api-image: ghcr.io/canonical/gnocchi-consolidated:2024.1 + gnocchi-metricd-image: ghcr.io/canonical/gnocchi-consolidated:2024.1 ceilometer: {% if ceilometer_k8s is defined and ceilometer_k8s is sameas true -%} charm: ../../../ceilometer-k8s.charm @@ -108,6 +108,18 @@ applications: aodh-notifier-image: ghcr.io/canonical/aodh-consolidated:2024.1 aodh-listener-image: ghcr.io/canonical/aodh-consolidated:2024.1 aodh-expirer-image: ghcr.io/canonical/aodh-consolidated:2024.1 + watcher: + {% if watcher_k8s is defined and watcher_k8s is sameas true -%} + charm: ../../../watcher-k8s.charm + {% else -%} + charm: ch:watcher-k8s + channel: 2024.1/edge + {% endif -%} + base: ubuntu@22.04 + scale: 1 + trust: true + resources: + watcher-image: ghcr.io/canonical/watcher-consolidated:2024.1 relations: - - mysql:database @@ -163,3 +175,16 @@ relations: - aodh:ingress-public - - keystone:send-ca-cert - aodh:receive-ca-cert + +- - mysql:database + - watcher:database +- - watcher:amqp + - rabbitmq:amqp +- - keystone:identity-service + - watcher:identity-service +- - traefik:ingress + - watcher:ingress-public +- - keystone:send-ca-cert + - watcher:receive-ca-cert +- - gnocchi:gnocchi-service + - watcher:gnocchi-db diff --git a/tests/ceph/tests.yaml b/tests/ceph/tests.yaml index caec8bdc..a927dfab 100644 --- a/tests/ceph/tests.yaml +++ b/tests/ceph/tests.yaml @@ -41,3 +41,6 @@ target_deploy_status: gnocchi: workload-status: blocked workload-status-message-regex: '^.*ceph.*$' + watcher: + workload-status: waiting + workload-status-message-regex: '^.*integration incomplete$' diff --git a/zuul.d/jobs.yaml b/zuul.d/jobs.yaml index 11d8e629..6f00290b 100644 --- a/zuul.d/jobs.yaml +++ b/zuul.d/jobs.yaml @@ -310,6 +310,18 @@ - rebuild vars: charm: sunbeam-clusterd +- job: + name: charm-build-watcher-k8s + description: Build sunbeam watcher-k8s charm + run: playbooks/charm/build.yaml + timeout: 3600 + match-on-config-updates: false + files: + - ops-sunbeam/ops_sunbeam/* + - charms/watcher-k8s/* + - rebuild + vars: + charm: watcher-k8s - job: name: func-test-core @@ -423,6 +435,8 @@ soft: true - name: charm-build-aodh-k8s soft: true + - name: charm-build-watcher-k8s + soft: true - name: charm-build-keystone-k8s soft: true files: @@ -432,6 +446,7 @@ - charms/gnocchi-k8s/* - charms/ceilometer-k8s/* - charms/aodh-k8s/* + - charms/watcher-k8s/* - rebuild vars: charm_jobs: @@ -440,6 +455,7 @@ - charm-build-gnocchi-k8s - charm-build-ceilometer-k8s - charm-build-aodh-k8s + - charm-build-watcher-k8s - charm-build-keystone-k8s test_dir: tests/ceph - job: @@ -883,3 +899,16 @@ secrets: - charmhub_token timeout: 3600 + +- job: + name: publish-charm-watcher-k8s + description: | + Publish watcher-k8s built in gate pipeline. + run: playbooks/charm/publish.yaml + files: + - ops-sunbeam/ops_sunbeam/* + - charms/watcher-k8s/* + - rebuild + secrets: + - charmhub_token + timeout: 3600 \ No newline at end of file diff --git a/zuul.d/project-templates.yaml b/zuul.d/project-templates.yaml index 80276bb1..9074130a 100644 --- a/zuul.d/project-templates.yaml +++ b/zuul.d/project-templates.yaml @@ -90,6 +90,8 @@ nodeset: ubuntu-jammy - charm-build-tempest-k8s: nodeset: ubuntu-jammy + - charm-build-watcher-k8s: + nodeset: ubuntu-jammy gate: fail-fast: true jobs: @@ -145,6 +147,8 @@ nodeset: ubuntu-jammy - charm-build-tempest-k8s: nodeset: ubuntu-jammy + - charm-build-watcher-k8s: + nodeset: ubuntu-jammy - project-template: name: charm-publish-jobs @@ -204,3 +208,5 @@ nodeset: ubuntu-jammy - publish-charm-tempest-k8s: nodeset: ubuntu-jammy + - publish-charm-watcher-k8s: + nodeset: ubuntu-jammy \ No newline at end of file diff --git a/zuul.d/secrets.yaml b/zuul.d/secrets.yaml index 53a1b727..c1f7b12e 100644 --- a/zuul.d/secrets.yaml +++ b/zuul.d/secrets.yaml @@ -1,75 +1,75 @@ - secret: name: charmhub_token data: - # Generated on 2024-07-11T12:47:55+00:00 with 90 days ttl + # Generated on 2024-09-05T09:40:42+00:00 with 90 days ttl value: !encrypted/pkcs1-oaep - - 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 diff --git a/zuul.d/zuul.yaml b/zuul.d/zuul.yaml index 6fcc995b..5cbe52f2 100644 --- a/zuul.d/zuul.yaml +++ b/zuul.d/zuul.yaml @@ -52,3 +52,4 @@ sunbeam-machine: 2024.1/edge sunbeam-clusterd: 2024.1/edge tempest-k8s: 2024.1/edge + watcher-k8s: 2024.1/edge