[openstack-hypervisor] Add list-nics action

Add action to list-nics on the hypervisor. This is used to gather
candidate for the external network nic.

Change-Id: Ife70804d035a900d5fe95059b26d3006860506da
Signed-off-by: Guillaume Boutry <guillaume.boutry@canonical.com>
This commit is contained in:
Guillaume Boutry 2024-10-15 19:49:41 +02:00
parent ef7c688448
commit de0e8bd10e
No known key found for this signature in database
GPG Key ID: E95E3326872E55DE
3 changed files with 102 additions and 1 deletions

View File

@ -58,6 +58,10 @@ actions:
type: string type: string
description: IP address to use for service configuration description: IP address to use for service configuration
additionalProperties: false additionalProperties: false
list-nics:
description: |
List host NICS, and which one are candidates for use as external NIC.
additionalProperties: false
requires: requires:
amqp: amqp:

View File

@ -27,6 +27,7 @@ import os
import secrets import secrets
import socket import socket
import string import string
import subprocess
from typing import ( from typing import (
List, List,
Optional, Optional,
@ -173,6 +174,10 @@ class HypervisorOperatorCharm(sunbeam_charm.OSBaseOperatorCharm):
self.on.set_hypervisor_local_settings_action, self.on.set_hypervisor_local_settings_action,
self._set_hypervisor_local_settings_action, self._set_hypervisor_local_settings_action,
) )
self.framework.observe(
self.on.list_nics_action,
self._list_nics_action,
)
self.framework.observe( self.framework.observe(
self.on.install, self.on.install,
self._on_install, self._on_install,
@ -320,6 +325,39 @@ class HypervisorOperatorCharm(sunbeam_charm.OSBaseOperatorCharm):
if new_snap_settings: if new_snap_settings:
self.set_snap_data(new_snap_settings) self.set_snap_data(new_snap_settings)
def _list_nics_action(self, event: ActionEvent):
"""Run list_nics action."""
cache = self.get_snap_cache()
hypervisor = cache["openstack-hypervisor"]
if not hypervisor.present:
event.fail("Hypervisor is not installed")
return
process = subprocess.run(
[
"snap",
"run",
"openstack-hypervisor",
"--verbose",
"list-nics",
"--format",
"json",
],
capture_output=True,
)
stderr = process.stderr.decode("utf-8")
logger.debug("logs: %s", stderr)
stdout = process.stdout.decode("utf-8")
logger.debug("stdout: %s", stdout)
if process.returncode != 0:
event.fail(stderr)
return
# cli returns a json dict with keys "nics" and "candidate"
event.set_results({"result": stdout})
def ensure_services_running(self): def ensure_services_running(self):
"""Ensure systemd services running.""" """Ensure systemd services running."""
# This should taken care of by the snap # This should taken care of by the snap

View File

@ -15,11 +15,14 @@
"""Tests for Openstack hypervisor charm.""" """Tests for Openstack hypervisor charm."""
import base64 import base64
import json
from unittest.mock import ( from unittest.mock import (
MagicMock, MagicMock,
) )
import charm import charm
import ops
import ops.testing
import ops_sunbeam.test_utils as test_utils import ops_sunbeam.test_utils as test_utils
@ -35,7 +38,13 @@ class _HypervisorOperatorCharm(charm.HypervisorOperatorCharm):
class TestCharm(test_utils.CharmTestCase): class TestCharm(test_utils.CharmTestCase):
"""Test charm to test relations.""" """Test charm to test relations."""
PATCHES = ["socket", "snap", "get_local_ip_by_default_route", "os"] PATCHES = [
"socket",
"snap",
"get_local_ip_by_default_route",
"os",
"subprocess",
]
def setUp(self): def setUp(self):
"""Setup OpenStack Hypervisor tests.""" """Setup OpenStack Hypervisor tests."""
@ -277,3 +286,53 @@ class TestCharm(test_utils.CharmTestCase):
"masakari.enable": True, "masakari.enable": True,
} }
hypervisor_snap_mock.set.assert_any_call(expect_settings, typed=True) hypervisor_snap_mock.set.assert_any_call(expect_settings, typed=True)
def test_list_nics_snap_not_installed(self):
"""Check action raises ActionFailed if snap is not installed."""
self.harness.begin()
hypervisor_snap_mock = MagicMock()
hypervisor_snap_mock.present = False
self.snap.SnapCache.return_value = {
"openstack-hypervisor": hypervisor_snap_mock
}
with self.assertRaises(ops.testing.ActionFailed):
self.harness.run_action("list-nics")
def test_list_nics(self):
"""Check action returns nics."""
self.harness.begin()
hypervisor_snap_mock = MagicMock()
hypervisor_snap_mock.present = True
self.snap.SnapCache.return_value = {
"openstack-hypervisor": hypervisor_snap_mock
}
subprocess_run_mock = MagicMock()
subprocess_run_mock.return_value = MagicMock(
stdout=bytes(
json.dumps({"nics": ["eth0", "eth1"], "candidates": ["eth2"]}),
"utf-8",
),
stderr=b"yes things went well",
returncode=0,
)
self.subprocess.run = subprocess_run_mock
action_output = self.harness.run_action("list-nics")
assert "candidates" in action_output.results["result"]
def test_list_nics_error(self):
"""Check action raises ActionFailed if subprocess returns non-zero."""
self.harness.begin()
hypervisor_snap_mock = MagicMock()
hypervisor_snap_mock.present = True
self.snap.SnapCache.return_value = {
"openstack-hypervisor": hypervisor_snap_mock
}
subprocess_run_mock = MagicMock()
subprocess_run_mock.return_value = MagicMock(
stdout=b"",
stderr=b"things did not go well",
returncode=1,
)
self.subprocess.run = subprocess_run_mock
with self.assertRaises(ops.testing.ActionFailed):
self.harness.run_action("list-nics")