Skip to content

Commit

Permalink
restoreconfig: add ability to restore/reload single target or storage…
Browse files Browse the repository at this point in the history
…_object

Problem:
-------
Right now, if any target or storage_object is failed to load as part of
target service bringup or manual restoreconfig, may be because of an issue from
the backend, then there is no way to reload that single storage_object or target,
we need to reload/restart all the targets/storage_objects present in the node
just to load one single target/storage_object, this interrupts on going
I/O (via this path) for all the volumes hosted within the node.

Solution:
--------
Add a capability to reload single target or storage_object without touching other
targets or storage_objects hosted in the node.

usage:

$ targetcli
targetcli shell version 2.1.fb49
Copyright 2011-2013 by Datera, Inc and others
For help on commands, type 'help'.

/> help
[...]

AVAILABLE COMMANDS
  The following commands are available in the current path:

[...]
    - restoreconfig [savefile] [clear_existing] [target] [storage_object]
[...]

Ex 1. reload single storage_object
$ targetcli restoreconfig /etc/target/saveconfig.json clear_existing \
    storage_object=blockx

Ex 2. reload single target
$ targetcli restoreconfig /etc/target/saveconfig.json clear_existing \
    target=iqn.2016-12.org.gluster-block:xxx

Ex 3. reload a given target and storage_object
$ targetcli restoreconfig /etc/target/saveconfig.json clear_existing \
    target=iqn.2016-12.org.gluster-block:xxx storage_object=blockx

Signed-off-by: Prasanna Kumar Kalever <prasanna.kalever@redhat.com>
  • Loading branch information
Prasanna Kumar Kalever committed Aug 23, 2019
1 parent f0194fc commit e3758d9
Showing 1 changed file with 94 additions and 40 deletions.
134 changes: 94 additions & 40 deletions rtslib/root.py
Expand Up @@ -269,7 +269,7 @@ def dump(self):
if f.discovery_enable_auth]
return d

def clear_existing(self, confirm=False):
def clear_existing(self, target=None, storage_object=None, confirm=False):
'''
Remove entire current configuration.
'''
Expand All @@ -278,26 +278,48 @@ def clear_existing(self, confirm=False):

# Targets depend on storage objects, delete them first.
for t in self.targets:
t.delete()
# * Delete the single matching target if target=iqn.xxx was supplied
# with restoreconfig command
# * If only storage_object=blockx option is supplied then do not
# delete any targets
# * If restoreconfig was not supplied with neither target=iqn.xxx
# nor storage_object=blockx then delete all targets
if (not storage_object and not target) or (target and t.wwn == target):
t.delete()
if target:
break

for fm in (f for f in self.fabric_modules if f.has_feature("discovery_auth")):
fm.clear_discovery_auth_settings()

for so in self.storage_objects:
so.delete()
# * Delete the single matching storage object if storage_object=blockx
# was supplied with restoreconfig command
# * If only target=iqn.xxx option is supplied then do not
# delete any storage_object's
# * If restoreconfig was not supplied with neither target=iqn.xxx
# nor storage_object=blockx then delete all storage_object's
if (not storage_object and not target) or (storage_object and so.name == storage_object):
so.delete()
if storage_object:
break

# If somehow some hbas still exist (no storage object within?) clean
# them up too.
for hba_dir in glob.glob("%s/core/*_*" % self.configfs_dir):
os.rmdir(hba_dir)
if not (storage_object or target):
for hba_dir in glob.glob("%s/core/*_*" % self.configfs_dir):
os.rmdir(hba_dir)

def restore(self, config, clear_existing=False, abort_on_error=False):
def restore(self, config, target=None, storage_object=None,
clear_existing=False, abort_on_error=False):
'''
Takes a dict generated by dump() and reconfigures the target to match.
Returns list of non-fatal errors that were encountered.
Will refuse to restore over an existing configuration unless clear_existing
is True.
'''
if clear_existing:
self.clear_existing(confirm=True)
self.clear_existing(target, storage_object, confirm=True)
elif any(self.storage_objects) or any(self.targets):
if any(self.storage_objects):
for config_so in config.get('storage_objects', []):
Expand All @@ -313,7 +335,6 @@ def restore(self, config, clear_existing=False, abort_on_error=False):
if config_tg['wwn'] == loaded_tg.wwn:
raise RTSLibError("target with wwn %s exist, not restoring"
%(loaded_tg.wwn))

errors = []

if abort_on_error:
Expand All @@ -327,30 +348,45 @@ def err_func(err_str):
if 'name' not in so:
err_func("'name' not defined in storage object %d" % index)
continue
try:
so_cls = so_mapping[so['plugin']]
except KeyError:
err_func("'plugin' not defined or invalid in storageobject %s" % so['name'])
continue
kwargs = so.copy()
dict_remove(kwargs, ('exists', 'attributes', 'plugin', 'buffered_mode', 'alua_tpgs'))
try:
so_obj = so_cls(**kwargs)
except Exception as e:
err_func("Could not create StorageObject %s: %s" % (so['name'], e))
continue

# Custom err func to include block name
def so_err_func(x):
return err_func("Storage Object %s/%s: %s" % (so['plugin'], so['name'], x))
# * Restore/load the single matching storage object if
# storage_object=blockx was supplied with restoreconfig command
# * In case if no storage_object was supplied but only target=iqn.xxx
# was supplied then do not load any storage_object's
# * If neither storage_object nor target option was supplied to
# restoreconfig, then go ahead and load all storage_object's
if (not storage_object and not target) or (storage_object and so['name'] == storage_object):
try:
so_cls = so_mapping[so['plugin']]
except KeyError:
err_func("'plugin' not defined or invalid in storageobject %s" % so['name'])
if storage_object:
break
continue
kwargs = so.copy()
dict_remove(kwargs, ('exists', 'attributes', 'plugin', 'buffered_mode', 'alua_tpgs'))
try:
so_obj = so_cls(**kwargs)
except Exception as e:
err_func("Could not create StorageObject %s: %s" % (so['name'], e))
if storage_object:
break
continue

set_attributes(so_obj, so.get('attributes', {}), so_err_func)
# Custom err func to include block name
def so_err_func(x):
return err_func("Storage Object %s/%s: %s" % (so['plugin'], so['name'], x))

for alua_tpg in so.get('alua_tpgs', {}):
try:
ALUATargetPortGroup.setup(so_obj, alua_tpg, err_func)
except RTSLibALUANotSupported:
pass
set_attributes(so_obj, so.get('attributes', {}), so_err_func)

for alua_tpg in so.get('alua_tpgs', {}):
try:
ALUATargetPortGroup.setup(so_obj, alua_tpg, err_func)
except RTSLibALUANotSupported:
pass

if storage_object:
break

# Don't need to create fabric modules
for index, fm in enumerate(config.get('fabric_modules', [])):
Expand All @@ -366,17 +402,32 @@ def so_err_func(x):
if 'wwn' not in t:
err_func("'wwn' not defined in target %d" % index)
continue
if 'fabric' not in t:
err_func("target %s missing 'fabric' field" % t['wwn'])
continue
if t['fabric'] not in (f.name for f in self.fabric_modules):
err_func("Unknown fabric '%s'" % t['fabric'])
continue

fm_obj = FabricModule(t['fabric'])
# * Restore/load the single matching target if target=iqn.xxx was
# supplied with restoreconfig command
# * In case if no target was supplied but only storage_object=blockx
# was supplied then do not load any targets
# * If neither storage_object nor target option was supplied to
# restoreconfig, then go ahead and load all targets
if (not storage_object and not target) or (target and t['wwn'] == target):
if 'fabric' not in t:
err_func("target %s missing 'fabric' field" % t['wwn'])
if target:
break
continue
if t['fabric'] not in (f.name for f in self.fabric_modules):
err_func("Unknown fabric '%s'" % t['fabric'])
if target:
break
continue

fm_obj = FabricModule(t['fabric'])

# Instantiate target
Target.setup(fm_obj, t, err_func)
# Instantiate target
Target.setup(fm_obj, t, err_func)

if target:
break

return errors

Expand All @@ -403,7 +454,9 @@ def save_to_file(self, save_file=None, so_path=None):

os.rename(save_file+".temp", save_file)

def restore_from_file(self, restore_file=None, clear_existing=True, abort_on_error=False):
def restore_from_file(self, restore_file=None, clear_existing=True,
target=None, storage_object=None,
abort_on_error=False):
'''
Restore the configuration from a file in json format.
Restore file defaults to '/etc/targets/saveconfig.json'.
Expand All @@ -415,7 +468,8 @@ def restore_from_file(self, restore_file=None, clear_existing=True, abort_on_err

with open(restore_file, "r") as f:
config = json.loads(f.read())
return self.restore(config, clear_existing=clear_existing,
return self.restore(config, target, storage_object,
clear_existing=clear_existing,
abort_on_error=abort_on_error)

def invalidate_caches(self):
Expand Down

0 comments on commit e3758d9

Please sign in to comment.