diff --git a/.gitreview b/.gitreview new file mode 100644 index 0000000..119e2c0 --- /dev/null +++ b/.gitreview @@ -0,0 +1,4 @@ +[gerrit] +host=review.opendev.org +port=29418 +project=openstack/charm-nova-compute-nvidia-vgpu diff --git a/.zuul.yaml b/.zuul.yaml new file mode 100644 index 0000000..fd20909 --- /dev/null +++ b/.zuul.yaml @@ -0,0 +1,4 @@ +- project: + templates: + - openstack-python3-charm-jobs + - openstack-cover-jobs diff --git a/README.md b/README.md index f4827b6..64433a9 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Overview -This subordinate charm provides the NVidia vGPU support to the +This subordinate charm provides the Nvidia vGPU support to the [OpenStack Nova Compute service][charm-nova-compute]. # Bugs diff --git a/build-requirements.txt b/build-requirements.txt index b9eab88..d549549 100644 --- a/build-requirements.txt +++ b/build-requirements.txt @@ -1,3 +1,5 @@ # NOTE(lourot): versions newer than 1.0.0 fail as described in # https://github.com/canonical/charmcraft/pull/487#issuecomment-894529408 git+https://github.com/canonical/charmcraft.git@1.0.0#egg=charmcraft + +cffi==1.14.6; python_version < '3.6' # cffi 1.15.0 drops support for py35. diff --git a/metadata.yaml b/metadata.yaml index 2c4522d..e288ead 100644 --- a/metadata.yaml +++ b/metadata.yaml @@ -1,10 +1,10 @@ name: nova-compute-nvidia-vgpu -summary: NVidia vGPU support for OpenStack Nova Compute +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. + This charm provides Nvidia vGPU support for Nova. tags: - openstack series: diff --git a/osci.yaml b/osci.yaml index 89f4fd2..cbdd00c 100644 --- a/osci.yaml +++ b/osci.yaml @@ -1,16 +1,89 @@ - project: templates: - charm-yoga-unit-jobs - - charm-yoga-functional-jobs - - charm-xena-functional-jobs - - charm-wallaby-functional-jobs - - charm-victoria-functional-jobs check: jobs: # NOTE(lourot): adding `focal-ussuri` manually to the list here instead # of using `charm-ussuri-functional-jobs` as we don't support Bionic. - - focal-ussuri + - jammy-yoga-nvidia-vgpu: + voting: false + - impish-xena-nvidia-vgpu + - hirsute-wallaby-nvidia-vgpu + - focal-yoga-nvidia-vgpu + - focal-xena-nvidia-vgpu + - focal-wallaby-nvidia-vgpu + - focal-victoria-nvidia-vgpu + - focal-ussuri-nvidia-vgpu vars: needs_charm_build: true charm_build_name: nova-compute-nvidia-vgpu build_type: charmcraft + +- job: + name: jammy-yoga-nvidia-vgpu + description: Run a functional test against jammy-yoga + parent: func-target + dependencies: &smoke-jobs + - focal-ussuri-nvidia-vgpu + vars: + tox_extra_args: jammy-yoga +- job: + name: impish-xena-nvidia-vgpu + description: Run a functional test against impish-xena + parent: func-target + dependencies: *smoke-jobs + vars: + tox_extra_args: impish-xena +- job: + name: hirsute-wallaby-nvidia-vgpu + description: Run a functional test against hirsute-wallaby + parent: func-target + dependencies: *smoke-jobs + vars: + tox_extra_args: hirsute-wallaby +- job: + name: focal-yoga-nvidia-vgpu + description: Run a functional test against focal-yoga + parent: func-target + dependencies: *smoke-jobs + vars: + tox_extra_args: focal-yoga +- job: + name: focal-xena-nvidia-vgpu + description: Run a functional test against focal-xena + parent: func-target + dependencies: *smoke-jobs + vars: + tox_extra_args: focal-xena +- job: + name: focal-wallaby-nvidia-vgpu + description: Run a functional test against focal-wallaby + parent: func-target + dependencies: *smoke-jobs + vars: + tox_extra_args: focal-wallaby +- job: + name: focal-victoria-nvidia-vgpu + description: Run a functional test against focal-victoria + parent: func-target + dependencies: *smoke-jobs + vars: + tox_extra_args: focal-victoria +- job: + name: focal-ussuri-nvidia-vgpu + description: Run a functional test against focal-ussuri + parent: func-target + dependencies: + # The soft dependencies mean that if they are not configured to run + # they will be ignored. See + # https://github.com/openstack-charmers/zosci-config + - charm-build + - osci-lint + - name: tox-py36 + soft: true + - name: tox-py38 + soft: true + - name: tox-py39 + soft: true + vars: + tox_extra_args: focal-ussuri diff --git a/src/charm.py b/src/charm.py index 8615507..bbaa431 100755 --- a/src/charm.py +++ b/src/charm.py @@ -173,20 +173,28 @@ class NovaComputeNvidiaVgpuCharm(ops_openstack.core.OSBaseCharm): return [package['version'] for package in apt_cache().dpkg_list(['nvidia-vgpu-ubuntu-*']).values()] - @staticmethod + @classmethod @cached - def _has_nvidia_gpu_hardware(): + def _has_nvidia_gpu_hardware(cls): """Search for NVIDIA GPU hardware. :returns: True if some NVIDIA GPU hardware is found on the current unit. :rtype: bool """ + return cls._has_nvidia_gpu_hardware_notcached() + + @staticmethod + def _has_nvidia_gpu_hardware_notcached(): nvidia_gpu_hardware_found = False for device in SimpleParser().run(): device_class = device.cls.name device_vendor = device.vendor.name - device_subsystem_vendor = device.subsystem_vendor.name + try: + device_subsystem_vendor = device.subsystem_vendor.name + except AttributeError: + device_subsystem_vendor = '' + if '3D' in device_class and ('NVIDIA' in device_vendor or 'NVIDIA' in device_subsystem_vendor): logging.debug('NVIDIA GPU found: {}'.format(device)) diff --git a/test-requirements.txt b/test-requirements.txt index 170df5e..e6f71d3 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -4,7 +4,7 @@ charm-tools>=2.4.4 coverage>=3.6 mock>=1.2 -flake8>=4.0.1 +flake8>=4.0.1; python_version >= '3.6' stestr>=2.2.0 requests>=2.18.4 psutil @@ -14,3 +14,4 @@ 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?) +cffi==1.14.6; python_version < '3.6' # cffi 1.15.0 drops support for py35. diff --git a/tests/bundles/focal-ussuri.yaml b/tests/bundles/focal-ussuri.yaml index 5693361..c951740 100644 --- a/tests/bundles/focal-ussuri.yaml +++ b/tests/bundles/focal-ussuri.yaml @@ -1,3 +1,5 @@ +local_overlay_enabled: False + variables: openstack-origin: &openstack-origin distro @@ -165,7 +167,7 @@ applications: - '10' nova-compute-nvidia-vgpu: - charm: ../../../nova-compute-nvidia-vgpu + charm: ../../nova-compute-nvidia-vgpu.charm relations: - - 'ceph-osd:mon' diff --git a/tests/bundles/focal-victoria.yaml b/tests/bundles/focal-victoria.yaml index 0724511..e771e32 100644 --- a/tests/bundles/focal-victoria.yaml +++ b/tests/bundles/focal-victoria.yaml @@ -1,3 +1,5 @@ +local_overlay_enabled: False + variables: openstack-origin: &openstack-origin cloud:focal-victoria @@ -165,7 +167,7 @@ applications: - '10' nova-compute-nvidia-vgpu: - charm: ../../../nova-compute-nvidia-vgpu + charm: ../../nova-compute-nvidia-vgpu.charm relations: - - 'ceph-osd:mon' diff --git a/tests/bundles/focal-wallaby.yaml b/tests/bundles/focal-wallaby.yaml index bd822a2..3c23367 100644 --- a/tests/bundles/focal-wallaby.yaml +++ b/tests/bundles/focal-wallaby.yaml @@ -1,3 +1,5 @@ +local_overlay_enabled: False + variables: openstack-origin: &openstack-origin cloud:focal-wallaby @@ -165,7 +167,7 @@ applications: - '10' nova-compute-nvidia-vgpu: - charm: ../../../nova-compute-nvidia-vgpu + charm: ../../nova-compute-nvidia-vgpu.charm relations: - - 'ceph-osd:mon' diff --git a/tests/bundles/focal-xena.yaml b/tests/bundles/focal-xena.yaml index 18a999f..310cf99 100644 --- a/tests/bundles/focal-xena.yaml +++ b/tests/bundles/focal-xena.yaml @@ -1,3 +1,5 @@ +local_overlay_enabled: False + variables: openstack-origin: &openstack-origin cloud:focal-xena @@ -165,7 +167,7 @@ applications: - '10' nova-compute-nvidia-vgpu: - charm: ../../../nova-compute-nvidia-vgpu + charm: ../../nova-compute-nvidia-vgpu.charm relations: - - 'ceph-osd:mon' diff --git a/tests/bundles/focal-yoga.yaml b/tests/bundles/focal-yoga.yaml index e4a782d..9c5443d 100644 --- a/tests/bundles/focal-yoga.yaml +++ b/tests/bundles/focal-yoga.yaml @@ -1,3 +1,5 @@ +local_overlay_enabled: False + variables: openstack-origin: &openstack-origin cloud:focal-yoga @@ -165,7 +167,7 @@ applications: - '10' nova-compute-nvidia-vgpu: - charm: ../../../nova-compute-nvidia-vgpu + charm: ../../nova-compute-nvidia-vgpu.charm relations: - - 'ceph-osd:mon' diff --git a/tests/bundles/hirsute-wallaby.yaml b/tests/bundles/hirsute-wallaby.yaml index d0530f4..a5ca6bc 100644 --- a/tests/bundles/hirsute-wallaby.yaml +++ b/tests/bundles/hirsute-wallaby.yaml @@ -1,3 +1,5 @@ +local_overlay_enabled: False + variables: openstack-origin: &openstack-origin distro @@ -165,7 +167,7 @@ applications: - '10' nova-compute-nvidia-vgpu: - charm: ../../../nova-compute-nvidia-vgpu + charm: ../../nova-compute-nvidia-vgpu.charm relations: - - 'ceph-osd:mon' diff --git a/tests/bundles/impish-xena.yaml b/tests/bundles/impish-xena.yaml index 9f66f99..8e02f9b 100644 --- a/tests/bundles/impish-xena.yaml +++ b/tests/bundles/impish-xena.yaml @@ -1,3 +1,5 @@ +local_overlay_enabled: False + variables: openstack-origin: &openstack-origin distro @@ -165,7 +167,7 @@ applications: - '10' nova-compute-nvidia-vgpu: - charm: ../../../nova-compute-nvidia-vgpu + charm: ../../nova-compute-nvidia-vgpu.charm relations: - - 'ceph-osd:mon' diff --git a/tests/bundles/jammy-yoga.yaml b/tests/bundles/jammy-yoga.yaml index 7ffab88..994b176 100644 --- a/tests/bundles/jammy-yoga.yaml +++ b/tests/bundles/jammy-yoga.yaml @@ -1,3 +1,5 @@ +local_overlay_enabled: False + variables: openstack-origin: &openstack-origin distro @@ -165,7 +167,7 @@ applications: - '10' nova-compute-nvidia-vgpu: - charm: ../../../nova-compute-nvidia-vgpu + charm: ../../nova-compute-nvidia-vgpu.charm relations: - - 'ceph-osd:mon' diff --git a/tests/tests.yaml b/tests/tests.yaml index 3a340ac..dd3decc 100644 --- a/tests/tests.yaml +++ b/tests/tests.yaml @@ -7,12 +7,12 @@ gate_bundles: - focal-ussuri - focal-victoria - focal-wallaby + - focal-xena - focal-yoga - hirsute-wallaby + - impish-xena dev_bundles: - - focal-xena - - impish-xena - jammy-yoga configure: diff --git a/tox.ini b/tox.ini index 99cf840..241a7b5 100644 --- a/tox.ini +++ b/tox.ini @@ -54,6 +54,11 @@ basepython = python3.8 deps = -r{toxinidir}/requirements.txt -r{toxinidir}/test-requirements.txt +[testenv:py39] +basepython = python3.9 +deps = -r{toxinidir}/requirements.txt + -r{toxinidir}/test-requirements.txt + [testenv:py3] basepython = python3 deps = -r{toxinidir}/requirements.txt diff --git a/unit_tests/test_charm.py b/unit_tests/test_charm.py index d44512f..f7ecfb2 100644 --- a/unit_tests/test_charm.py +++ b/unit_tests/test_charm.py @@ -13,23 +13,95 @@ # limitations under the License. import unittest -from src.charm import NovaComputeNvidiaVgpuCharm + +from mock import MagicMock, patch + from ops.model import ActiveStatus from ops.testing import Harness +import src.charm -class TestNovaComputeNvidiaVgpuCharm(unittest.TestCase): + +class CharmTestCase(unittest.TestCase): + + def setUp(self, obj, patches): + super().setUp() + self.patches = patches + self.obj = obj + self.patch_all() + + def patch(self, method): + _m = patch.object(self.obj, method) + mock = _m.start() + self.addCleanup(_m.stop) + return mock + + def patch_all(self): + for method in self.patches: + setattr(self, method, self.patch(method)) + + +class MockLspciProperty: + def __init__(self, name): + self.name = name + + +class MockLspciDevice: + def __init__(self, cls_name, vendor_name): + self.cls = MockLspciProperty(cls_name) + self.vendor = MockLspciProperty(vendor_name) + + +class TestNovaComputeNvidiaVgpuCharm(CharmTestCase): + + _PATCHES = [ + 'SimpleParser', + ] + + _PCI_DEVICES_LIST_WITHOUT_GPU = [ + # This is an NVIDIA device, but not a GPU card: + MockLspciDevice(cls_name='VGA compatible controller', + vendor_name='NVIDIA Corporation'), + ] + + _PCI_DEVICES_LIST_WITH_NVIDIA_GPU = [ + # This is an NVIDIA device, but not a GPU card: + MockLspciDevice(cls_name='VGA compatible controller', + vendor_name='NVIDIA Corporation'), + # This is an NVIDIA GPU card: + MockLspciDevice(cls_name='3D controller', + vendor_name='NVIDIA Corporation'), + ] def setUp(self): - self.harness = Harness(NovaComputeNvidiaVgpuCharm) + super().setUp(src.charm, self._PATCHES) + self.harness = Harness(src.charm.NovaComputeNvidiaVgpuCharm) self.addCleanup(self.harness.cleanup) self.harness.begin() - def test_start(self): + def test_has_nvidia_gpu_hardware_with_hw(self): + self.SimpleParser.return_value = MagicMock() + self.SimpleParser.return_value.run.return_value = ( + self._PCI_DEVICES_LIST_WITH_NVIDIA_GPU) + self.assertTrue( + self.harness.charm._has_nvidia_gpu_hardware_notcached()) + + def test_has_nvidia_gpu_hardware_without_hw(self): + self.SimpleParser.return_value = MagicMock() + self.SimpleParser.return_value.run.return_value = ( + self._PCI_DEVICES_LIST_WITHOUT_GPU) + self.assertFalse( + self.harness.charm._has_nvidia_gpu_hardware_notcached()) + + def test_init(self): self.assertEqual( self.harness.framework.model.app.name, 'nova-compute-nvidia-vgpu') - # Test that charm is active upon installation. + self.assertFalse(self.harness.charm._stored.is_started) + self.assertIsNone( + self.harness.charm._stored.last_installed_resource_hash) + + def test_start(self): self.harness.charm.on.start.emit() self.assertTrue(isinstance( self.harness.model.unit.status, ActiveStatus))