From e0a7e85b3a6e3c390f9852b1da2823a54c179466 Mon Sep 17 00:00:00 2001 From: Aurelien Lourot Date: Mon, 8 Nov 2021 16:03:27 +0100 Subject: [PATCH] Initial Operator framework boilerplate Change-Id: I31352b72a2a113b79eac575571c99dbe61bea474 --- .gitignore | 9 ++ .stestr.conf | 3 + build-requirements.txt | 1 + config.yaml | 1 + metadata.yaml | 24 ++++ pip.sh | 18 +++ requirements.txt | 1 + src/charm.py | 39 ++++++ src/test-requirements.txt | 9 ++ src/tox.ini | 61 ++++++++ test-requirements.txt | 16 +++ tests/README.md | 10 ++ tests/bundles/focal-xena.yaml | 254 ++++++++++++++++++++++++++++++++++ tests/tests.yaml | 18 +++ tox.ini | 133 ++++++++++++++++++ unit_tests/__init__.py | 13 ++ unit_tests/test_charm.py | 35 +++++ 17 files changed, 645 insertions(+) create mode 100644 .gitignore create mode 100644 .stestr.conf create mode 100644 build-requirements.txt create mode 100644 config.yaml create mode 100644 metadata.yaml create mode 100755 pip.sh create mode 100644 requirements.txt create mode 100755 src/charm.py create mode 100644 src/test-requirements.txt create mode 100644 src/tox.ini create mode 100644 test-requirements.txt create mode 100644 tests/README.md create mode 100644 tests/bundles/focal-xena.yaml create mode 100644 tests/tests.yaml create mode 100644 tox.ini create mode 100644 unit_tests/__init__.py create mode 100644 unit_tests/test_charm.py diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0be3a4a --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +build +layers +.tox +interfaces +.testrepository +.stestr +*__pycache__* +*.pyc +/nova-compute-nvidia-vgpu.charm diff --git a/.stestr.conf b/.stestr.conf new file mode 100644 index 0000000..5fcccac --- /dev/null +++ b/.stestr.conf @@ -0,0 +1,3 @@ +[DEFAULT] +test_path=./unit_tests +top_dir=./ diff --git a/build-requirements.txt b/build-requirements.txt new file mode 100644 index 0000000..271d895 --- /dev/null +++ b/build-requirements.txt @@ -0,0 +1 @@ +git+https://github.com/canonical/charmcraft.git@0.10.2#egg=charmcraft diff --git a/config.yaml b/config.yaml new file mode 100644 index 0000000..a985d46 --- /dev/null +++ b/config.yaml @@ -0,0 +1 @@ +options: {} diff --git a/metadata.yaml b/metadata.yaml new file mode 100644 index 0000000..8692543 --- /dev/null +++ b/metadata.yaml @@ -0,0 +1,24 @@ +name: nova-compute-nvidia-vgpu +summary: NVidia vGPU support for OpenStack Nova Compute +maintainer: OpenStack Charmers +description: | + OpenStack Compute, codenamed Nova, is a cloud computing fabric controller. + . + This charm provides NVidia vGPU support for Nova. +tags: +- openstack +series: +- bionic +- focal +- groovy +- hirsute +- impish +subordinate: true +provides: + nova-compute-subordinate: + interface: nova-compute-subordinate + scope: container +requires: + juju-info: + interface: juju-info + scope: container diff --git a/pip.sh b/pip.sh new file mode 100755 index 0000000..9a7e6b0 --- /dev/null +++ b/pip.sh @@ -0,0 +1,18 @@ +#!/usr/bin/env bash +# +# This file is managed centrally by release-tools and should not be modified +# within individual charm repos. See the 'global' dir contents for available +# choices of tox.ini for OpenStack Charms: +# https://github.com/openstack-charmers/release-tools +# +# setuptools 58.0 dropped the support for use_2to3=true which is needed to +# install blessings (an indirect dependency of charm-tools). +# +# More details on the beahvior of tox and virtualenv creation can be found at +# https://github.com/tox-dev/tox/issues/448 +# +# This script is wrapper to force the use of the pinned versions early in the +# process when the virtualenv was created and upgraded before installing the +# depedencies declared in the target. +pip install 'pip<20.3' 'setuptools<50.0.0' +pip "$@" diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..2d81d3b --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +ops diff --git a/src/charm.py b/src/charm.py new file mode 100755 index 0000000..d06c2ea --- /dev/null +++ b/src/charm.py @@ -0,0 +1,39 @@ +#! /usr/bin/env python3 + +# Copyright 2021 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. + + +import json + +from ops.charm import CharmBase +from ops.framework import StoredState +from ops.main import main +from ops.model import ActiveStatus + + +class NovaComputeNvidiaVgpuCharm(CharmBase): + + _stored = StoredState() + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.framework.observe(self.on.install, self._on_install) + + def _on_install(self, _): + self.unit.status = ActiveStatus('Unit is ready') + + +if __name__ == '__main__': + main(NovaComputeNvidiaVgpuCharm) diff --git a/src/test-requirements.txt b/src/test-requirements.txt new file mode 100644 index 0000000..e771023 --- /dev/null +++ b/src/test-requirements.txt @@ -0,0 +1,9 @@ +# This file is managed centrally by release-tools and should not be modified +# within individual charm repos. See the 'global' dir contents for available +# choices of *requirements.txt files for OpenStack Charms: +# https://github.com/openstack-charmers/release-tools +# + +# Functional Test Requirements (let Zaza's dependencies solve all dependencies here!) +git+https://github.com/openstack-charmers/zaza.git#egg=zaza +git+https://github.com/openstack-charmers/zaza-openstack-tests.git#egg=zaza.openstack diff --git a/src/tox.ini b/src/tox.ini new file mode 100644 index 0000000..b40d295 --- /dev/null +++ b/src/tox.ini @@ -0,0 +1,61 @@ +# Source charm (with zaza): ./src/tox.ini +# This file is managed centrally by release-tools and should not be modified +# within individual charm repos. See the 'global' dir contents for available +# choices of tox.ini for OpenStack Charms: +# https://github.com/openstack-charmers/release-tools + +[tox] +envlist = pep8 +skipsdist = True +# NOTE: Avoid build/test env pollution by not enabling sitepackages. +sitepackages = False +# NOTE: Avoid false positives by not skipping missing interpreters. +skip_missing_interpreters = False +# NOTES: +# * We avoid the new dependency resolver by pinning pip < 20.3, see +# https://github.com/pypa/pip/issues/9187 +# * Pinning dependencies requires tox >= 3.2.0, see +# https://tox.readthedocs.io/en/latest/config.html#conf-requires +# * It is also necessary to pin virtualenv as a newer virtualenv would still +# lead to fetching the latest pip in the func* tox targets, see +# https://stackoverflow.com/a/38133283 +requires = pip < 20.3 + virtualenv < 20.0 +# NOTE: https://wiki.canonical.com/engineering/OpenStack/InstallLatestToxOnOsci +minversion = 3.18.0 + +[testenv] +setenv = VIRTUAL_ENV={envdir} + PYTHONHASHSEED=0 +allowlist_externals = juju +passenv = HOME TERM CS_* OS_* TEST_* +deps = -r{toxinidir}/test-requirements.txt +install_command = + pip install {opts} {packages} + +[testenv:pep8] +basepython = python3 +commands = charm-proof + +[testenv:func-noop] +basepython = python3 +commands = + functest-run-suite --help + +[testenv:func] +basepython = python3 +commands = + functest-run-suite --keep-model + +[testenv:func-smoke] +basepython = python3 +commands = + functest-run-suite --keep-model --smoke + +[testenv:func-target] +basepython = python3 +commands = + functest-run-suite --keep-model --bundle {posargs} + +[testenv:venv] +commands = {posargs} diff --git a/test-requirements.txt b/test-requirements.txt new file mode 100644 index 0000000..358e1bc --- /dev/null +++ b/test-requirements.txt @@ -0,0 +1,16 @@ +# This file is managed centrally. If you find the need to modify this as a +# one-off, please don't. Intead, consult #openstack-charms and ask about +# requirements management in charms via bot-control. Thank you. +charm-tools>=2.4.4 +coverage>=3.6 +mock>=1.2 +flake8>=2.2.4,<=2.4.1 +stestr>=2.2.0 +requests>=2.18.4 +psutil +# oslo.i18n dropped py35 support +oslo.i18n<4.0.0 +git+https://github.com/openstack-charmers/zaza.git#egg=zaza +git+https://github.com/openstack-charmers/zaza-openstack-tests.git#egg=zaza.openstack +pytz # workaround for 14.04 pip/tox +pyudev # for ceph-* charm unit tests (not mocked?) diff --git a/tests/README.md b/tests/README.md new file mode 100644 index 0000000..cbb3f27 --- /dev/null +++ b/tests/README.md @@ -0,0 +1,10 @@ +# Overview + +This directory provides Zaza test definitions and bundles to verify basic +deployment functionality from the perspective of this charm, its requirements +and its features, as exercised in a subset of the full OpenStack deployment +test bundle topology. + +For full details on functional testing of OpenStack charms please refer to +the [functional testing](https://docs.openstack.org/charm-guide/latest/reference/testing.html#functional-testing) +section of the OpenStack Charm Guide. diff --git a/tests/bundles/focal-xena.yaml b/tests/bundles/focal-xena.yaml new file mode 100644 index 0000000..18a999f --- /dev/null +++ b/tests/bundles/focal-xena.yaml @@ -0,0 +1,254 @@ +variables: + openstack-origin: &openstack-origin cloud:focal-xena + +series: focal + +comment: +- 'machines section to decide order of deployment. database sooner = faster' +machines: + '0': + constraints: mem=3072M + '1': + constraints: mem=3072M + '2': + constraints: mem=3072M + '3': + '4': + constraints: mem=4096M + '5': + '6': + '7': + '8': + '9': + '10': + constraints: mem=4096M cores=4 + '11': + '12': + '13': + '14': + '15': + '16': + '17': + '18': + '19': + +applications: + + nova-cloud-controller-mysql-router: + charm: cs:~openstack-charmers-next/mysql-router + keystone-mysql-router: + charm: cs:~openstack-charmers-next/mysql-router + glance-mysql-router: + charm: cs:~openstack-charmers-next/mysql-router + neutron-api-mysql-router: + charm: cs:~openstack-charmers-next/mysql-router + placement-mysql-router: + charm: cs:~openstack-charmers-next/mysql-router + + mysql-innodb-cluster: + charm: cs:~openstack-charmers-next/mysql-innodb-cluster + num_units: 3 + options: + source: *openstack-origin + to: + - '0' + - '1' + - '2' + + ceph-osd: + charm: cs:~openstack-charmers-next/ceph-osd + num_units: 6 + storage: + osd-devices: '10G' + options: + source: *openstack-origin + to: + - '11' + - '12' + - '13' + - '14' + - '15' + - '16' + + ceph-mon: + charm: cs:~openstack-charmers-next/ceph-mon + num_units: 3 + options: + source: *openstack-origin + monitor-count: '3' + to: + - '17' + - '18' + - '19' + + rabbitmq-server: + charm: cs:~openstack-charmers-next/rabbitmq-server + num_units: 1 + options: + source: *openstack-origin + to: + - '3' + + nova-cloud-controller: + charm: cs:~openstack-charmers-next/nova-cloud-controller + num_units: 1 + options: + openstack-origin: *openstack-origin + network-manager: Neutron + debug: true + to: + - '4' + + neutron-api: + charm: cs:~openstack-charmers-next/neutron-api + num_units: 1 + options: + manage-neutron-plugin-legacy-mode: true + openstack-origin: *openstack-origin + flat-network-providers: physnet1 + neutron-security-groups: true + to: + - '5' + + keystone: + charm: cs:~openstack-charmers-next/keystone + num_units: 1 + options: + openstack-origin: *openstack-origin + to: + - '6' + + neutron-gateway: + charm: cs:~openstack-charmers-next/neutron-gateway + num_units: 1 + options: + openstack-origin: *openstack-origin + bridge-mappings: physnet1:br-ex + to: + - '7' + + glance: + charm: cs:~openstack-charmers-next/glance + num_units: 1 + options: + openstack-origin: *openstack-origin + to: + - '8' + + neutron-openvswitch: + charm: cs:~openstack-charmers-next/neutron-openvswitch + + placement: + charm: cs:~openstack-charmers-next/placement + num_units: 1 + options: + openstack-origin: *openstack-origin + to: + - '9' + + nova-compute: + charm: cs:~openstack-charmers-next/nova-compute + num_units: 1 + storage: + ephemeral-device: '40G' + options: + openstack-origin: *openstack-origin + config-flags: auto_assign_floating_ip=False + enable-live-migration: false + aa-profile-mode: enforce + debug: true + pool-type: erasure-coded + ec-profile-k: 4 + ec-profile-m: 2 + libvirt-image-backend: rbd + to: + - '10' + + nova-compute-nvidia-vgpu: + charm: ../../../nova-compute-nvidia-vgpu + +relations: + - - 'ceph-osd:mon' + - 'ceph-mon:osd' + + - - 'nova-compute:ceph' + - 'ceph-mon:client' + + - - 'nova-compute:image-service' + - 'glance:image-service' + + - - 'nova-compute:amqp' + - 'rabbitmq-server:amqp' + + - - 'nova-cloud-controller:shared-db' + - 'nova-cloud-controller-mysql-router:shared-db' + - - 'nova-cloud-controller-mysql-router:db-router' + - 'mysql-innodb-cluster:db-router' + + - - 'nova-cloud-controller:identity-service' + - 'keystone:identity-service' + + - - 'nova-cloud-controller:amqp' + - 'rabbitmq-server:amqp' + + - - 'nova-cloud-controller:cloud-compute' + - 'nova-compute:cloud-compute' + + - - 'nova-cloud-controller:image-service' + - 'glance:image-service' + + - - 'keystone:shared-db' + - 'keystone-mysql-router:shared-db' + - - 'keystone-mysql-router:db-router' + - 'mysql-innodb-cluster:db-router' + + - - 'glance:identity-service' + - 'keystone:identity-service' + + - - 'glance:shared-db' + - 'glance-mysql-router:shared-db' + - - 'glance-mysql-router:db-router' + - 'mysql-innodb-cluster:db-router' + + - - 'glance:amqp' + - 'rabbitmq-server:amqp' + + - - 'neutron-gateway:amqp' + - 'rabbitmq-server:amqp' + + - - 'nova-cloud-controller:quantum-network-service' + - 'neutron-gateway:quantum-network-service' + + - - 'neutron-api:shared-db' + - 'neutron-api-mysql-router:shared-db' + - - 'neutron-api-mysql-router:db-router' + - 'mysql-innodb-cluster:db-router' + + - - 'neutron-api:amqp' + - 'rabbitmq-server:amqp' + + - - 'neutron-api:neutron-api' + - 'nova-cloud-controller:neutron-api' + + - - 'neutron-api:identity-service' + - 'keystone:identity-service' + + - - 'nova-compute:neutron-plugin' + - 'neutron-openvswitch:neutron-plugin' + + - - 'rabbitmq-server:amqp' + - 'neutron-openvswitch:amqp' + + - - 'placement:shared-db' + - 'placement-mysql-router:shared-db' + - - 'placement-mysql-router:db-router' + - 'mysql-innodb-cluster:db-router' + + - - 'placement:identity-service' + - 'keystone:identity-service' + + - - 'placement:placement' + - 'nova-cloud-controller:placement' + + - - 'nova-compute' + - 'nova-compute-nvidia-vgpu' diff --git a/tests/tests.yaml b/tests/tests.yaml new file mode 100644 index 0000000..0895eb0 --- /dev/null +++ b/tests/tests.yaml @@ -0,0 +1,18 @@ +charm_name: nova-compute-nvidia-vgpu + +smoke_bundles: + - focal-xena + +gate_bundles: + - focal-xena + +configure: + - zaza.openstack.charm_tests.glance.setup.add_cirros_image + - zaza.openstack.charm_tests.glance.setup.add_lts_image + - zaza.openstack.charm_tests.keystone.setup.add_demo_user + - zaza.openstack.charm_tests.neutron.setup.basic_overcloud_network + - zaza.openstack.charm_tests.nova.setup.create_flavors + - zaza.openstack.charm_tests.nova.setup.manage_ssh_key + +tests: + - zaza.openstack.charm_tests.nova.tests.LTSGuestCreateTest diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..775ea57 --- /dev/null +++ b/tox.ini @@ -0,0 +1,133 @@ +# Operator charm (with zaza): tox.ini + +[tox] +envlist = pep8,py3 +skipsdist = True +# NOTE: Avoid build/test env pollution by not enabling sitepackages. +sitepackages = False +# NOTE: Avoid false positives by not skipping missing interpreters. +skip_missing_interpreters = False +# NOTES: +# * We avoid the new dependency resolver by pinning pip < 20.3, see +# https://github.com/pypa/pip/issues/9187 +# * Pinning dependencies requires tox >= 3.2.0, see +# https://tox.readthedocs.io/en/latest/config.html#conf-requires +# * It is also necessary to pin virtualenv as a newer virtualenv would still +# lead to fetching the latest pip in the func* tox targets, see +# https://stackoverflow.com/a/38133283 +requires = pip < 20.3 + virtualenv < 20.0 +# NOTE: https://wiki.canonical.com/engineering/OpenStack/InstallLatestToxOnOsci +minversion = 3.2.0 + +[testenv] +setenv = VIRTUAL_ENV={envdir} + PYTHONHASHSEED=0 + CHARM_DIR={envdir} +install_command = + pip install {opts} {packages} +commands = stestr run --slowest {posargs} +whitelist_externals = + git + add-to-archive.py + bash +passenv = HOME TERM CS_* OS_* TEST_* +deps = -r{toxinidir}/test-requirements.txt + +[testenv:py35] +basepython = python3.5 +# python3.5 is irrelevant on a focal+ charm. +commands = /bin/true + +[testenv:py36] +basepython = python3.6 +deps = -r{toxinidir}/requirements.txt + -r{toxinidir}/test-requirements.txt + +[testenv:py37] +basepython = python3.7 +deps = -r{toxinidir}/requirements.txt + -r{toxinidir}/test-requirements.txt + +[testenv:py38] +basepython = python3.8 +deps = -r{toxinidir}/requirements.txt + -r{toxinidir}/test-requirements.txt + +[testenv:py3] +basepython = python3 +deps = -r{toxinidir}/requirements.txt + -r{toxinidir}/test-requirements.txt + +[testenv:pep8] +basepython = python3 +deps = -r{toxinidir}/requirements.txt + -r{toxinidir}/test-requirements.txt +commands = flake8 {posargs} src unit_tests tests + +[testenv:cover] +# Technique based heavily upon +# https://github.com/openstack/nova/blob/master/tox.ini +basepython = python3 +deps = -r{toxinidir}/requirements.txt + -r{toxinidir}/test-requirements.txt +setenv = + {[testenv]setenv} + PYTHON=coverage run +commands = + coverage erase + stestr run --slowest {posargs} + coverage combine + coverage html -d cover + coverage xml -o cover/coverage.xml + coverage report + +[coverage:run] +branch = True +concurrency = multiprocessing +parallel = True +source = + . +omit = + .tox/* + */charmhelpers/* + unit_tests/* + +[testenv:venv] +basepython = python3 +commands = {posargs} + +[testenv:build] +basepython = python3 +deps = -r{toxinidir}/build-requirements.txt +commands = + charmcraft build + +[testenv:func-noop] +basepython = python3 +commands = + functest-run-suite --help + +[testenv:func] +basepython = python3 +commands = + functest-run-suite --keep-model + +[testenv:func-smoke] +basepython = python3 +commands = + functest-run-suite --keep-model --smoke + +[testenv:func-dev] +basepython = python3 +commands = + functest-run-suite --keep-model --dev + +[testenv:func-target] +basepython = python3 +commands = + functest-run-suite --keep-model --bundle {posargs} + +[flake8] +# Ignore E902 because the unit_tests directory is missing in the built charm. +ignore = E402,E226,E902 diff --git a/unit_tests/__init__.py b/unit_tests/__init__.py new file mode 100644 index 0000000..8381d13 --- /dev/null +++ b/unit_tests/__init__.py @@ -0,0 +1,13 @@ +# Copyright 2021 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. diff --git a/unit_tests/test_charm.py b/unit_tests/test_charm.py new file mode 100644 index 0000000..22f49fc --- /dev/null +++ b/unit_tests/test_charm.py @@ -0,0 +1,35 @@ +# Copyright 2016 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. + +import unittest +from src.charm import NovaComputeNvidiaVgpuCharm +from ops.model import ActiveStatus +from ops.testing import Harness + + +class TestNovaComputeNvidiaVgpuCharm(unittest.TestCase): + + def setUp(self): + self.harness = Harness(NovaComputeNvidiaVgpuCharm) + self.addCleanup(self.harness.cleanup) + self.harness.begin() + + def test_install(self): + self.assertEqual( + self.harness.framework.model.app.name, + 'nova-compute-nvidia-vgpu') + # Test that charm is active upon installation. + self.harness.charm.on.install.emit() + self.assertTrue(isinstance( + self.harness.model.unit.status, ActiveStatus))