charms.openstack/charms_openstack/charm/utils.py

82 lines
3.0 KiB
Python

import json
import hashlib
import charmhelpers.core as core
import charmhelpers.fetch as fetch
# TODO: drop once charmhelpers releases a new version
# with this function in the fetch helper (> 0.9.1)
def get_upstream_version(package):
"""Determine upstream version based on installed package
@returns None (if not installed) or the upstream version
"""
import apt_pkg
cache = fetch.apt_cache()
try:
pkg = cache[package]
except:
# the package is unknown to the current apt cache.
return None
if not pkg.current_ver:
# package is known, but no version is currently installed.
return None
return apt_pkg.upstream_version(pkg.current_ver.ver_str)
# TODO(AJK): Once this is in charms.reactive, drop it here and just reference
# the charms.reactive version.
# NOTE(AJK): that we are breaking the camalcase rule as this is acting as a
# context manager, which doesn't look like a 'class'
class is_data_changed(object):
""" Check if the given set of data has changed since the previous call.
This works by hashing the JSON-serialization of the data. Note that, while
the data will be serialized using ``sort_keys=True``, some types of data
structures, such as sets, may lead to false positivies.
The hash of the changed data WON'T be stored until a successful exit of the
context manager. This means if the code in the scope of the context
manager raises an exception, then the data won't be changed and the next
check will leave it unchanged. This is to allow for recovery from errors.
Usage:
with is_data_changed() as changed:
charm_instance.some_method()
"""
def __init__(self, data_id, data, hash_type='md5',
no_change_on_exception=True):
"""Initialise the context manager:
@param data_id: <String> the unique name for this data
@param data: a JSON serialisable data object.
@param hash_type: A hashing function available from hashlib
default='md5'
@param no_change_on_exception: if an exception is thrown in the managed
code then the new hash is not persisited.
"""
self.data_id = data_id
self.data = data
self.hash_type = hash_type
self.no_change_on_exception = no_change_on_exception
def __enter__(self):
"""with statement as returns boolean"""
self.key = 'charms.openstack.data_changed.{}'.format(self.data_id)
alg = getattr(hashlib, self.hash_type)
serialized = json.dumps(self.data, sort_keys=True).encode('utf8')
old_hash = core.unitdata.kv().get(self.key)
self.new_hash = alg(serialized).hexdigest()
return old_hash != self.new_hash
def __exit__(self, e_type, *_):
# If no exception, then store the new hash.
if e_type is None or not self.no_change_on_exception:
core.unitdata.kv().set(self.key, self.new_hash)
# re-raise the exception, if there was one.
return False