17. Implement CharmSpecific.is_compatible
is_compatible determines if the refresh from the old to new workload and charm code versions is supported.
@classmethod
def is_compatible(
cls,
*,
old_charm_version: charm_refresh.CharmVersion,
new_charm_version: charm_refresh.CharmVersion,
old_workload_version: str,
new_workload_version: str,
) -> bool:
is_compatible is called on the new charm code version. (This means that is_compatible is responsible for determining which versions the charm code supports refreshing from—not refreshing to.)
is_compatible must not use any information outside of its parameters to determine if the refresh is compatible.
is_compatible must always return True if the old and new charm code versions are identical and the old and new workload versions are identical (so that rollbacks are compatible).
Charm version
The charm is required to support refreshing to and rollback from newer versions of the charm:
it should be possible to refresh from any charm released to stable in a Charmhub track to any (semantically) newer charm released to stable in the same track. (It should also be possible to rollback.)
It is strongly recommended to not support charm code downgrades.
This will make backwards compatibility much easier.
If you use the recommended approach, add this code to your CharmSpecific class to report the charm code’s compatibility:
@classmethod
def is_compatible(
cls,
*,
old_charm_version: charm_refresh.CharmVersion,
new_charm_version: charm_refresh.CharmVersion,
old_workload_version: str,
new_workload_version: str,
) -> bool:
# Check charm version compatibility
if not super().is_compatible(
old_charm_version=old_charm_version,
new_charm_version=new_charm_version,
old_workload_version=old_workload_version,
new_workload_version=new_workload_version,
):
return False
For a charm with a Kubernetes variant & a machine variant that share code, ensure that this code is placed in your class that directly inherits from charm_refresh.CharmSpecificCommon |
@dataclasses.dataclass(eq=False)
class PostgreSQLRefresh(charm_refresh.CharmSpecificCommon, abc.ABC):
@classmethod
def is_compatible(
cls,
*,
old_charm_version: charm_refresh.CharmVersion,
new_charm_version: charm_refresh.CharmVersion,
old_workload_version: str,
new_workload_version: str,
) -> bool:
# Check charm version compatibility
if not super().is_compatible(
old_charm_version=old_charm_version,
new_charm_version=new_charm_version,
old_workload_version=old_workload_version,
new_workload_version=new_workload_version,
):
return False
Workload version
Workload compatibility is determined by the upstream workload. It may also be affected by changes to the workload packaging.
If an upstream workload supports in-place major version upgrades & rollbacks, it is recommended for charms to not support this by default. Instead, it is recommended to only support (if at all) in-place major workload version refreshes between two specific versions (of the charm code and workload) that have been thoroughly tested.
Workload compatibility does not always follow simple rules.
For example: historically, MySQL has broken backwards compatibility in patch releases.
Charm developers are responsible for ensuring that is_compatible reflects reality
|
@dataclasses.dataclass(eq=False)
class PostgreSQLRefresh(charm_refresh.CharmSpecificCommon, abc.ABC):
@classmethod
def is_compatible(
cls,
*,
old_charm_version: charm_refresh.CharmVersion,
new_charm_version: charm_refresh.CharmVersion,
old_workload_version: str,
new_workload_version: str,
) -> bool:
# Check charm version compatibility
if not super().is_compatible(
old_charm_version=old_charm_version,
new_charm_version=new_charm_version,
old_workload_version=old_workload_version,
new_workload_version=new_workload_version,
):
return False
# Check workload version compatibility
old_major, old_minor = (int(component) for component in old_workload_version.split("."))
new_major, new_minor = (int(component) for component in new_workload_version.split("."))
if old_major != new_major:
return False
return new_minor >= old_minor