diff --git a/charms/openstack-hypervisor/src/charm.py b/charms/openstack-hypervisor/src/charm.py index aaef581b..259dbf6c 100755 --- a/charms/openstack-hypervisor/src/charm.py +++ b/charms/openstack-hypervisor/src/charm.py @@ -21,6 +21,7 @@ This charm provide hypervisor services as part of an OpenStack deployment """ import base64 +import functools import logging import os import secrets @@ -121,10 +122,16 @@ class MTlsCertificatesHandler(sunbeam_rhandlers.TlsCertificatesHandler): if csr is None: return {} + main_key = self._private_keys.get("main") + if main_key is None: + # this can happen when the relation is removed + # or unit is departing + logger.debug("No main key found") + return {} for cert in certs: if cert.csr == csr: return { - "key": self._private_keys["main"], + "key": main_key, "cert": cert.certificate, "ca_cert": cert.ca, "ca_with_intermediates": cert.ca @@ -307,21 +314,21 @@ class HypervisorOperatorCharm(sunbeam_charm.OSBaseOperatorCharm): def set_snap_data(self, snap_data: dict): """Set snap data on local snap.""" - cache = snap.SnapCache() + cache = self.get_snap_cache() hypervisor = cache["openstack-hypervisor"] new_settings = {} - for k in sorted(snap_data.keys()): - try: - if snap_data[k] != hypervisor.get(k, typed=True): - new_settings[k] = snap_data[k] - except snap.SnapError: - # Trying to retrieve an unset parameter results in a snapError - # so assume the snap.SnapError means there is missing config - # that needs setting. - # Setting a value to None will unset the value from the snap, - # which will fail if the value was never set. - if snap_data[k] is not None: - new_settings[k] = snap_data[k] + old_settings = hypervisor.get(None, typed=True) + for key, new_value in snap_data.items(): + group, subkey = key.split(".") + if ( + old_value := old_settings.get(group, {}).get(subkey) + ) is not None: + if old_value != new_value: + new_settings[key] = new_value + # Setting a value to None will unset the value from the snap, + # which will fail if the value was never set. + elif new_value is not None: + new_settings[key] = new_value if new_settings: logger.debug(f"Applying new snap settings {new_settings}") hypervisor.set(new_settings, typed=True) @@ -332,7 +339,7 @@ class HypervisorOperatorCharm(sunbeam_charm.OSBaseOperatorCharm): """Install snap if it is not already present.""" config = self.model.config.get try: - cache = snap.SnapCache() + cache = self.get_snap_cache() hypervisor = cache["openstack-hypervisor"] if not hypervisor.present: @@ -345,6 +352,11 @@ class HypervisorOperatorCharm(sunbeam_charm.OSBaseOperatorCharm): e.message, ) + @functools.cache + def get_snap_cache(self) -> snap.SnapCache: + """Return snap cache.""" + return snap.SnapCache() + def configure_unit(self, event) -> None: """Run configuration on this unit.""" self.check_leader_ready() diff --git a/libs/external/lib/charms/operator_libs_linux/v2/snap.py b/libs/external/lib/charms/operator_libs_linux/v2/snap.py index ef426775..9d09a78d 100644 --- a/libs/external/lib/charms/operator_libs_linux/v2/snap.py +++ b/libs/external/lib/charms/operator_libs_linux/v2/snap.py @@ -83,7 +83,7 @@ LIBAPI = 2 # Increment this PATCH version before using `charmcraft publish-lib` or reset # to 0 if you are raising the major API version -LIBPATCH = 5 +LIBPATCH = 7 # Regex to locate 7-bit C1 ANSI sequences @@ -319,7 +319,10 @@ class Snap(object): Default is to return a string. """ if typed: - config = json.loads(self._snap("get", ["-d", key])) + args = ["-d"] + if key: + args.append(key) + config = json.loads(self._snap("get", args)) if key: return config.get(key) return config @@ -584,13 +587,16 @@ class Snap(object): "Installing snap %s, revision %s, tracking %s", self._name, revision, channel ) self._install(channel, cohort, revision) - else: + logger.info("The snap installation completed successfully") + elif revision is None or revision != self._revision: # The snap is installed, but we are changing it (e.g., switching channels). logger.info( "Refreshing snap %s, revision %s, tracking %s", self._name, revision, channel ) self._refresh(channel=channel, cohort=cohort, revision=revision, devmode=devmode) - logger.info("The snap installation completed successfully") + logger.info("The snap refresh completed successfully") + else: + logger.info("Refresh of snap %s was unnecessary", self._name) self._update_snap_apps() self._state = state