Add channel config option

The charm uses a hardcoded channel declared in src/layer.yaml with the
value 1.0/stable, this prevents users from overriding it. This became a
blocker with the release of octavia-diskimage-retrofit 2.0 in the `2.0`
track to support Ubuntu Noble 24.04, the snap migrated to core24[0][1]

This change introduces a new configuration option that defaults to null,
this will allow the charm to use the 1.0/stable channel when running on
Jammy and 2.0/stable when running on Noble. Existing environments
running on Jammy will see no behavior change, and no operator's
intervention is needed when upgrading.

[0] https://github.com/openstack-charmers/octavia-diskimage-retrofit/pull/44
[1] https://github.com/openstack-charmers/octavia-diskimage-retrofit/pull/46

Change-Id: I1ccba9de844f7900c9a8517fff4de2f8c4019871
Signed-off-by: Felipe Reyes <felipe.reyes@canonical.com>

Co-Authored-by: Hemanth Nakkina <hemanth.nakkina@canonical.com>
Change-Id: I1ccba9de844f7900c9a8517fff4de2f8c4019871
Signed-off-by: Hemanth Nakkina <hemanth.nakkina@canonical.com>
This commit is contained in:
Felipe Reyes
2026-06-22 13:41:28 +05:30
committed by Hemanth Nakkina
co-authored by Hemanth Nakkina
parent 93ee45ba54
commit 6671969b97
6 changed files with 107 additions and 7 deletions
+5
View File
@@ -1,4 +1,9 @@
options:
channel:
type: string
default:
description: >-
The snap channel to install from.
ubuntu-mirror:
type: string
default: ''
-4
View File
@@ -9,10 +9,6 @@ options:
basic:
use_venv: True
include_system_packages: False
snap:
octavia-diskimage-retrofit:
channel: 1.0/stable
classic: true
comment: |
Using devmode pending resolution of snapd fuse-support issue
https://github.com/openstack-charmers/octavia-diskimage-retrofit/issues/6
@@ -17,6 +17,20 @@ import charms.reactive as reactive
import charms_openstack.bus
import charms_openstack.charm as charm
from collections import defaultdict
from charms.layer import snap
from charms.reactive.flags import (
set_flag,
clear_flag,
)
from charmhelpers.core.hookenv import (
config,
log,
)
from charmhelpers.core.host import (
get_distrib_codename,
)
charms_openstack.bus.discover()
charm.use_defaults(
@@ -26,6 +40,34 @@ charm.use_defaults(
'upgrade-charm',
)
CHANNELS = defaultdict(lambda: 'latest/edge')
CHANNELS.update({
'jammy': '1.0/stable',
'noble': '2.0/stable',
})
@reactive.when_not('snap.installed.octavia-diskimage-retrofit')
def snap_install():
channel = config('channel')
if not channel:
series = get_distrib_codename()
channel = CHANNELS[series]
log('No snap channel configured, using default '
'for series {}: {}'.format(series, channel),
level="INFO")
if validate_snap_risk(channel):
clear_flag('snap.channel.invalid')
snap.install('core')
snap.install('octavia-diskimage-retrofit',
channel=channel,
classic=True)
else:
log('Invalid snap channel risk level: {}'.format(channel),
level="ERROR")
set_flag('snap.channel.invalid')
@reactive.when('identity-credentials.connected')
@reactive.when_not('identity-credentials.available')
@@ -48,3 +90,24 @@ def credentials_available():
def retrofit_by_cron():
with charm.provide_charm_instance() as instance:
instance.handle_auto_retrofit()
def validate_snap_risk(channel):
"""Validate a provided snap channel's risk
Any prefix is ignored ('0.10' in '0.10/stable' for example).
:param: channel: string of the snap channel to validate
:returns: boolean: whether provided channel is valid
"""
tokens = channel.split('/')
if len(tokens) == 1:
risk_level = tokens[0]
else:
# Check if track is a risk level (invalid case like 'stable/edge')
track = tokens[0]
risk_level = tokens[1]
if track in ('stable', 'candidate', 'beta', 'edge'):
return False
return risk_level in ('stable', 'candidate', 'beta', 'edge')
+4 -3
View File
@@ -68,7 +68,7 @@ applications:
path: 'streams/v1/index.sjson',
max: 1,
item_filters: [
'release~(jammy)',
'release~(noble)',
'arch~(x86_64|amd64)',
'ftype~(disk1.img|disk.img)'
]
@@ -103,8 +103,9 @@ applications:
octavia-diskimage-retrofit:
charm: ../../../octavia-diskimage-retrofit.charm
options:
retrofit-uca-pocket: antelope
retrofit-series: jammy
channel: 2.0/edge
retrofit-uca-pocket: caracal
retrofit-series: noble
relations:
+8
View File
@@ -30,3 +30,11 @@ sys.modules['glanceclient'] = glanceclient
sys.modules['keystoneauth1'] = keystoneauth1
sys.modules['keystoneauth1.loading'] = keystoneauth1.loading
sys.modules['keystoneauth1.session'] = keystoneauth1.session
# Mock out charms.layer modules
charms_layer = mock.MagicMock()
charms_layer_snap = mock.MagicMock()
charms_layer_basic = mock.MagicMock()
sys.modules['charms.layer'] = charms_layer
sys.modules['charms.layer.snap'] = charms_layer_snap
sys.modules['charms.layer.basic'] = charms_layer_basic
@@ -12,6 +12,7 @@
# See the License for the specific language governing permissions and
# limitations under the License.
import unittest
from unittest import mock
import reactive.octavia_diskimage_retrofit_handlers as handlers
@@ -45,6 +46,8 @@ class TestRegisteredHooks(test_utils.TestRegisteredHooks):
'when_not': {
'request_credentials': (
'identity-credentials.available',),
'snap_install': (
'snap.installed.octavia-diskimage-retrofit',),
},
}
# test that the hooks were registered via the
@@ -76,3 +79,27 @@ class TestOctaviaDiskimageRetrofitHandlers(test_utils.PatchHelper):
def test_credentials_available(self):
handlers.credentials_available()
self.charm_instance.assess_status.assert_called_once_with()
class TestValidateSnapRisk(unittest.TestCase):
def test_valid_risk_only(self):
for risk in ('stable', 'candidate', 'beta', 'edge'):
with self.subTest(risk=risk):
self.assertTrue(handlers.validate_snap_risk(risk))
def test_valid_channel_with_track(self):
self.assertTrue(handlers.validate_snap_risk('0.10/stable'))
self.assertTrue(handlers.validate_snap_risk('latest/edge'))
self.assertTrue(handlers.validate_snap_risk('1.0/candidate'))
self.assertTrue(handlers.validate_snap_risk('2/beta'))
def test_invalid_risk_only(self):
self.assertFalse(handlers.validate_snap_risk('invalid'))
self.assertFalse(handlers.validate_snap_risk(''))
self.assertFalse(handlers.validate_snap_risk('stable/edge'))
def test_invalid_risk_with_track(self):
self.assertFalse(handlers.validate_snap_risk('0.10/invalid'))
self.assertFalse(handlers.validate_snap_risk('latest/'))
self.assertFalse(handlers.validate_snap_risk('latest/nightly'))