Optimize access to the snap data

Access to each key took around 15 to 20ms, with more than 50 keys it
adds up, This code path is often executed. Get all keys from snapd at
once, and compare with values to set.
Fix issue when unit is departing and private key is removed.

Change-Id: I655a79ece3f09271532e0f41914141532474f2ac
Signed-off-by: Guillaume Boutry <guillaume.boutry@canonical.com>
This commit is contained in:
Guillaume Boutry 2024-07-30 17:46:58 +02:00
parent 2c8e658693
commit a9e24c4580
No known key found for this signature in database
GPG Key ID: E95E3326872E55DE
2 changed files with 37 additions and 19 deletions

View File

@ -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()

View File

@ -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