Skip to content

Commit

Permalink
Allow rolling back host path snapshots
Browse files Browse the repository at this point in the history
  • Loading branch information
sonicaj committed Dec 20, 2024
1 parent bea1e6e commit ab635be
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 4 deletions.
4 changes: 2 additions & 2 deletions src/middlewared/middlewared/plugins/apps/resources.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,9 +145,9 @@ async def gpu_choices_internal(self):

@private
async def get_hostpaths_datasets(self, app_name):
app_info = self.middleware.call_sync('app.get_instance', app_name)
app_info = await self.middleware.call('app.get_instance', app_name)
host_paths = [
volume['source_path'] for volume in app_info['active_workloads']['volumes']
volume['source'] for volume in app_info['active_workloads']['volumes']
if volume['source'].startswith(f'{IX_APPS_MOUNT_PATH}/') is False
]

Expand Down
34 changes: 34 additions & 0 deletions src/middlewared/middlewared/plugins/apps/rollback.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import logging

from middlewared.schema import accepts, Bool, Dict, List, Ref, returns, Str
from middlewared.service import job, Service, ValidationErrors

Expand All @@ -8,6 +10,9 @@
from .ix_apps.rollback import clean_newer_versions, get_rollback_versions


logger = logging.getLogger('app_lifecycle')


class AppService(Service):

class Config:
Expand All @@ -20,6 +25,7 @@ class Config:
'options',
Str('app_version', empty=False, required=True),
Bool('rollback_snapshot', default=True),
Bool('rollback_hostpath_snapshots', default=False),
),
roles=['APPS_WRITE'],
)
Expand All @@ -36,6 +42,9 @@ def rollback(self, job, app_name, options):
elif options['app_version'] not in get_rollback_versions(app_name, app['version']):
verrors.add('options.app_version', 'Specified version is not available for rollback')

if app['state'] == 'STOPPED':
verrors.add('app_name', 'App must not be in stopped state to rollback')

verrors.check()

rollback_version = self.middleware.call_sync(
Expand Down Expand Up @@ -64,6 +73,8 @@ def rollback(self, job, app_name, options):
self.middleware.send_event(
'app.query', 'CHANGED', id=app_name, fields=self.middleware.call_sync('app.get_instance', app_name)
)
host_path_mapping = self.middleware.call_sync('app.get_hostpaths_datasets', app_name)
self.middleware.call_sync('app.stop', app_name).wait_sync()
try:
if options['rollback_snapshot'] and (
app_volume_ds := self.middleware.call_sync('app.get_app_volume_ds', app_name)
Expand All @@ -81,6 +92,29 @@ def rollback(self, job, app_name, options):
}
)

if options['rollback_hostpath_snapshots']:
if host_path_mapping:
logger.debug('Rolling back hostpath snapshots for %r app', app_name)

for host_path, dataset in host_path_mapping.items():
if not dataset:
logger.debug('No dataset found for %r hostpath', host_path)
continue

snap_name = f'{dataset}@{options["app_version"]}'
if self.middleware.call_sync('zfs.snapshot.query', [['id', '=', snap_name]]):
self.middleware.call_sync(
'zfs.snapshot.rollback', snap_name, {
'force': True,
'recursive': False,
'recursive_clones': False,
'recursive_rollback': False,
}
)
logger.debug('Rolled back %r snapshot for %r hostpath', snap_name, host_path)
else:
logger.debug('Snapshot %r not found for %r app', snap_name, app_name)

compose_action(app_name, options['app_version'], 'up', force_recreate=True, remove_orphans=True)
finally:
self.middleware.call_sync('app.metadata.generate').wait_sync(raise_error=True)
Expand Down
4 changes: 2 additions & 2 deletions src/middlewared/middlewared/plugins/apps/upgrade.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ class Config:
@private
def take_snapshot_of_hostpath(self, app, snapshot_hostpath):
app_info = self.middleware.call_sync('app.get_instance', app) if isinstance(app, str) else app
host_path_mapping = self.middleware.call_sync('app.get_host_path_mapping', app_info['name'])
host_path_mapping = self.middleware.call_sync('app.get_hostpaths_datasets', app_info['name'])
# Stop the app itself before we attempt to take snapshots
self.middleware.call_sync('app.stop', app_info['name']).wait_sync()
if not snapshot_hostpath:
Expand Down Expand Up @@ -52,7 +52,7 @@ def take_snapshot_of_hostpath(self, app, snapshot_hostpath):
continue

self.middleware.call_sync('zfs.snapshot.create', {'dataset': dataset, 'name': app_info['version']})

logger.debug('Created snapshot %r for %r app', snap_name, app_info['name'])

@accepts(
Str('app_name'),
Expand Down

0 comments on commit ab635be

Please sign in to comment.