# # Copyright (c) 2018 Red Hat, Inc. # # This program is free software; you can redistribute it and/or # modify it under the terms of the GNU General Public License # as published by the Free Software Foundation; either version 2 # of the License, or (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA # 02110-1301, USA. # """ VDOOperation - an object representing a vdo script command $Id: //eng/vdo-releases/magnesium/src/python/vdo/vdomgmnt/VDOOperation.py#9 $ """ from . import ArgumentError from . import CommandLock from . import Configuration from . import Constants from . import Defaults from . import MgmntLogger from . import MgmntUtils from . import Service from . import VDOKernelModuleService from . import VDOService, VDOServiceError, VDOServicePreviousOperationError from . import ExitStatus, SystemExitStatus, UserExitStatus from utils import Command, CommandError, runCommand from utils import Transaction, transactional from functools import partial import inspect import __main__ as main import os import re import sys import yaml vdoOperations = dict() def lock(isExclusive, func, *args, **kwargs): commandArgs = args[1] confFile = os.path.abspath(commandArgs.confFile) confFile = os.path.realpath(confFile) # N.B.: We don't filter out shell special characters like " (){}"! # # Also, no quoting; if two config file names use "/" and "_" such # that they both map to the same lock file, so be it. lockFileBase = confFile.replace('/', '_') + '.lock' lockFile = os.path.join(Constants.LOCK_DIR, lockFileBase) with CommandLock(lockFile, isExclusive): return func(*args, **kwargs) def exclusivelock(func): "Decorator that locks the configuration for exclusive (write) access." def wrap(*args, **kwargs): return lock(False, func, *args, **kwargs) return wrap def sharedlock(func): "Decorator that locks the configuration for shared (read) access." def wrap(*args, **kwargs): return lock(False, func, *args, **kwargs) return wrap ######################################################################## class OperationError(ExitStatus, Exception): """Exception raised to indicate an error executing an operation.""" ###################################################################### # Public methods ###################################################################### ###################################################################### # Overridden methods ###################################################################### ###################################################################### # Protected methods ###################################################################### def __init__(self, msg, *args, **kwargs): super(OperationError, self).__init__(*args, **kwargs) self._msg = msg def __str__(self): return self._msg ######################################################################## class VDOOperation(object): """Every instance of this class runs one of the subcommands requested when 'vdo [] ' is called via the execute() method.""" ###################################################################### # Public methods ###################################################################### def getVdoServices(self, args, conf): """Return a list of VDOService objects to be operated on depending on the settings of the --name and --all options. Arguments: args: The arguments passed into vdo. conf: The config file Raises: ArgumentError """ services = [] if args.all: services.extend(conf.getAllVdos().values()) else: self._checkForName(args) services.append(conf.getVdo(args.name)) return services ###################################################################### def applyToVDOs(self, args, method, **kwargs): """Apply a method to all specified VDOs. An exception applying the method to some VDO will not prevent it from being applied to any other VDO, however any exception will result in applyToVDOs raising an exception. If the 'readonly' keyword argument is False, the configuration will be persisted after the method has been applied to all the VDOs (whether or not it succeeded for any of them). Arguments: args (dict): The command line arguments method (callable): The method to call on each VDO; will be called as method(args, vdo) kwargs: Keyword args controlling what gets returned and to use when making the Configuration """ exception = None conf = None if kwargs.get('readonly', True): conf = Configuration(self.confFile, **kwargs) else: conf = Configuration.modifiableSingleton(self.confFile) for vdo in self.getVdoServices(args, conf): try: method(args, vdo) except Exception as ex: exception = ex if not kwargs.get('readonly', True): conf.persist() if exception is not None: #pylint: disable=E0702 raise exception ###################################################################### def execute(self, unused_args): """Execute this operation. This method should be overridden by operation classes, and is intended only to be called from within the run() method below. Arguments: unused_args (dict): The command line arguments (used by subclasses) Raises: NotImplementedError """ self.log.error(_("{0} unimplemented").format(self.name)) raise NotImplementedError ###################################################################### def preflight(self, args): """Perform checks prior to actually executing the command. Arguments: args (dict): The command line arguments (used by subclasses) Raises: OperationError """ if self.requiresRoot and (os.getuid() != 0): msg = _("You must be root to use the \"{0}\" command").format(self.name) raise OperationError(msg, exitStatus = UserExitStatus) if self.checkBinaries: for executable in ['vdodumpconfig', 'vdoforcerebuild', 'vdoformat']: if not MgmntUtils.which(executable): msg = _("executable '{0}' not found in $PATH").format(executable) raise OperationError(msg, exitStatus = SystemExitStatus) if self.requiresRunMode and Command.noRunMode(): msg = _("{0} command not available with --noRun").format(self.name) raise OperationError(msg, exitStatus = UserExitStatus) ###################################################################### def run(self, args): """Run this operation. This is the external entry point for users of VDOOperation. Arguments: args (dict): The command line arguments """ self.confFile = Defaults.confFile try: self.confFile = args.confFile except KeyError: pass self.preflight(args) self.execute(args) ###################################################################### # Overridden methods ###################################################################### def __init__(self, **kwargs): name = re.sub('Operation$', '', type(self).__name__) self.name = name[0].lower() + name[1:] self.log = MgmntLogger.getLogger(MgmntLogger.myname + '.VDOOperations') self.requiresRoot = kwargs.get('requiresRoot', True) self.checkBinaries = kwargs.get('checkBinaries', False) self.requiresRunMode = kwargs.get('requiresRunMode', False) vdoOperations[self.name] = self ###################################################################### # Protected methods ###################################################################### def _checkForName(self, args): """Check that the args contain a non-None name. Arguments: args: the args passed Raises: ArgumentError """ if (args.name is None) or (args.name.strip() == ""): raise ArgumentError(_("Missing required argument '--name'")) ######################################################################## class ActivateOperation(VDOOperation): """Implements the activate command.""" ###################################################################### # Overridden methods ###################################################################### def __init__(self): super(ActivateOperation, self).__init__() ###################################################################### @exclusivelock def execute(self, args): self.applyToVDOs(args, self._activate, readonly=False) ###################################################################### # Protected methods ###################################################################### def _activate(self, args, vdo): vdo.activate() ######################################################################## class ChangeWritePolicyOperation(VDOOperation): """Implements the changeWritePolicy command.""" ###################################################################### # Overridden methods ###################################################################### def __init__(self): super(ChangeWritePolicyOperation, self).__init__() ###################################################################### @exclusivelock def execute(self, args): self._newWritePolicy = getattr(args, 'writePolicy') if self._newWritePolicy is None: return self.applyToVDOs(args, self._changeWritePolicy, readonly=False) ###################################################################### # Protected methods ###################################################################### def _changeWritePolicy(self, args, vdo): vdo.setWritePolicy(self._newWritePolicy) ######################################################################## class CreateOperation(VDOOperation): """Implements the create command.""" ###################################################################### # Overridden methods ###################################################################### def __init__(self): super(CreateOperation, self).__init__(checkBinaries=True) ###################################################################### def preflight(self, args): super(CreateOperation, self).preflight(args) if not args.name: raise ArgumentError(_("Missing required argument '--name'")) if not args.device: raise ArgumentError(_("Missing required argument '--device'")) ###################################################################### @exclusivelock @transactional def execute(self, args): # Get configuration conf = Configuration.modifiableSingleton(self.confFile) argsDict = vars(args).copy() name = argsDict['name'] del argsDict['name'] vdo = VDOService(args.name, conf, **argsDict) transaction = Transaction.transaction() vdo.create(args.force) transaction.addUndoStage(vdo.remove) conf.persist() vdo.announceReady() ######################################################################## class DeactivateOperation(VDOOperation): """Implements the deactivate command.""" ###################################################################### # Overridden methods ###################################################################### def __init__(self): super(DeactivateOperation, self).__init__() ###################################################################### @exclusivelock def execute(self, args): self.applyToVDOs(args, self._deactivate, readonly=False) ###################################################################### # Protected methods ###################################################################### def _deactivate(self, args, vdo): vdo.deactivate() ######################################################################## class GrowLogicalOperation(VDOOperation): """Implements the growLogical command.""" ###################################################################### # Overridden methods ###################################################################### def __init__(self): super(GrowLogicalOperation, self).__init__() ###################################################################### @exclusivelock def execute(self, args): conf = Configuration.modifiableSingleton(self.confFile) self._checkForName(args) vdo = conf.getVdo(args.name) vdo.growLogical(args.vdoLogicalSize) conf.persist() ######################################################################## class GrowPhysicalOperation(VDOOperation): """Implements the growPhysical command.""" ###################################################################### # Overridden methods ###################################################################### def __init__(self): super(GrowPhysicalOperation, self).__init__() ###################################################################### @exclusivelock def execute(self, args): conf = Configuration.modifiableSingleton(self.confFile) self._checkForName(args) vdo = conf.getVdo(args.name) vdo.growPhysical() conf.persist() ######################################################################## class ListOperation(VDOOperation): """Implements the list command.""" ###################################################################### # Overridden methods ###################################################################### def __init__(self): super(ListOperation, self).__init__() ###################################################################### def execute(self, args): vdos = set() for line in runCommand(['dmsetup', 'status'], noThrow=True).splitlines(): m = re.match(r"(.+?): \d \d+ " + Defaults.vdoTargetName, line) if m: vdos.add(m.group(1)) if args.all: conf = Configuration(self.confFile) vdos |= set(conf.getAllVdos().keys()) # We want to provide a stable ordering and a set, while great for # avoiding duplicates, doesn't guarantee ordering. So, make a list # from the set and sort it. vdos = list(vdos) vdos.sort() print(os.linesep.join(vdos)) ######################################################################## class ModifyOperation(VDOOperation): """Implements the modify command.""" ###################################################################### # Overridden methods ###################################################################### def __init__(self): super(ModifyOperation, self).__init__() ###################################################################### @exclusivelock def execute(self, args): self.applyToVDOs(args, self._modifyVDO, readonly=False) ###################################################################### def preflight(self, args): super(ModifyOperation, self).preflight(args) # Validate that the user didn't specify anything that can't be changed. VDOService.validateModifiableOptions(args) ###################################################################### # Protected methods ###################################################################### def _modifyVDO(self, args, vdo): vdo.setModifiableOptions(args) ######################################################################## class PrintConfigFileOperation(VDOOperation): """Implements the printConfigFile command.""" ###################################################################### # Overridden methods ###################################################################### def __init__(self): super(PrintConfigFileOperation, self).__init__(requiresRoot=False, requiresRunMode=True) ###################################################################### @sharedlock def execute(self, args): #pylint: disable=R0201 conf = Configuration(self.confFile, mustExist=True) print(conf.asYAMLForUser()) ######################################################################## class RemoveOperation(VDOOperation): """Implements the remove command.""" ###################################################################### # Overridden methods ###################################################################### def __init__(self): super(RemoveOperation, self).__init__() ###################################################################### @exclusivelock def execute(self, args): """Implements the remove command.""" self.applyToVDOs(args, self._removeVDO, readonly=False) ###################################################################### # Protected methods ###################################################################### def _removeVDO(self, args, vdo): removeSteps = [] try: vdo.remove(args.force, removeSteps = removeSteps) except VDOServicePreviousOperationError: print(_("A previous operation failed.")) print(_("Recovery from the failure either failed or was interrupted.")) print(_("Add '--force' to 'remove' to perform the following cleanup.")) print(os.linesep.join(removeSteps)) raise ######################################################################## class StartOperation(VDOOperation): """Implements the start command.""" ###################################################################### # Overridden methods ###################################################################### def __init__(self): super(StartOperation, self).__init__(checkBinaries=True) ###################################################################### @exclusivelock def execute(self, args): self.applyToVDOs(args, self._startVDO, readonly=False) ###################################################################### # Protected methods ###################################################################### @transactional def _startVDO(self, args, vdo): vdo.start(args.forceRebuild) vdo.announceReady(False) ######################################################################## class StatusOperation(VDOOperation): """Implements the status command.""" ###################################################################### # Overridden methods ###################################################################### def __init__(self): super(StatusOperation, self).__init__(requiresRoot=False, checkBinaries=True, requiresRunMode=True) ###################################################################### @exclusivelock def execute(self, args): #pylint: disable=R0201 conf = Configuration(self.confFile, readonly=False) if not args.name: args.all = True try: # To be consistent with previous output we must present each section as # its own rather than organizing them into one structure to dump. # Also, we gather all the info before printing it out to avoid # interspersing command info when run in verbose mode. values = {} vdoStatus = { _("VDO status") : values } values[_("Node")] = runCommand(['uname', '-n'], noThrow=True, strip=True) values[_("Date")] = runCommand(['date', '--rfc-3339=seconds'], noThrow=True, strip=True) if os.getuid() != 0: values[_("Note")] = _("Not running as root," + " some status may be unavailable") kernelStatus = { _("Kernel module") : VDOKernelModuleService().status() } confStatus = { _("Configuration") : conf.status() } vdos = {} perVdoStatus = { _("VDOs") : vdos } for vdo in self.getVdoServices(args, conf): try: vdos[vdo.getName()] = vdo.status() except VDOServiceError as ex: vdos[vdo.getName()] = str(ex) # YAML adds a newline at the end. To maintain consistency with the # previous output we need to eliminate that. print(yaml.safe_dump(vdoStatus, default_flow_style = False)[:-1]) print(yaml.safe_dump(kernelStatus, default_flow_style = False)[:-1]) print(yaml.safe_dump(confStatus, default_flow_style = False)[:-1]) print(yaml.safe_dump(perVdoStatus, default_flow_style = False, width=float("inf"))[:-1]) sys.stdout.flush() sys.stderr.flush() except IOError as ex: self.log.debug("exception ignored: {0}".format(ex)) ######################################################################## class StopOperation(VDOOperation): """Implements the stop command.""" ###################################################################### # Overridden methods ###################################################################### def __init__(self): super(StopOperation, self).__init__() ###################################################################### @exclusivelock def execute(self, args): self.applyToVDOs(args, self._stopVDO, readonly=False) ###################################################################### # Protected methods ###################################################################### def _stopVDO(self, args, vdo): vdo.stop(args.force) ######################################################################## class VersionOperation(VDOOperation): """Implements the version command.""" ###################################################################### # Overridden methods ###################################################################### def __init__(self): super(VersionOperation, self).__init__() ###################################################################### def execute(self, unused_args): kms = VDOKernelModuleService() kms.start() print(kms.version()) ######################################################################## class OptionToggle(VDOOperation): """Base class for operations which either enable or disable an option.""" ###################################################################### # Overridden methods ###################################################################### def __init__(self, enable, optionName, checkBinaries=False): super(OptionToggle, self).__init__(checkBinaries=checkBinaries) self._enable = enable self._optionName = optionName ###################################################################### @exclusivelock def execute(self, args): self.applyToVDOs(args, self._configure, readonly=False) ###################################################################### # Protected methods ###################################################################### def _configure(self, args, vdo): """Actually update the configuration for this operation. This method must be overridden by derived classes.""" self.log.error(_("{0} unimplemented").format(self.name)) raise NotImplementedError ######################################################################## class DisableCompressionOperation(OptionToggle): """Implements the disableCompression command.""" ###################################################################### # Overridden methods ###################################################################### def __init__(self): super(DisableCompressionOperation, self).__init__(False, "compression") ###################################################################### def _configure(self, args, vdo): vdo.setCompression(False) ######################################################################## class DisableDeduplicationOperation(OptionToggle): """Implements the disable deduplication command.""" ###################################################################### # Protected methods ###################################################################### def __init__(self): super(DisableDeduplicationOperation, self).__init__(False, "deduplication") ###################################################################### def _configure(self, args, vdo): vdo.setDeduplication(False) ######################################################################## class EnableCompressionOperation(OptionToggle): """Implements the enableCompression command.""" ###################################################################### # Overridden methods ###################################################################### def __init__(self): super(EnableCompressionOperation, self).__init__(True, "compression") ###################################################################### def _configure(self, args, vdo): vdo.setCompression(True) ######################################################################## class EnableDeduplicationOperation(OptionToggle): """Implements the enableDeduplication command.""" ###################################################################### # Overridden methods ###################################################################### def __init__(self): super(EnableDeduplicationOperation, self).__init__(True, "deduplication", checkBinaries=True) ###################################################################### def _configure(self, args, vdo): vdo.setDeduplication(True) ######################################################################## operationRE = re.compile("^[A-Z].*Operation$") ######################################################################## def _isOperation(member): return (inspect.isclass(member) and (member.__name__ != "VDOOperation") and operationRE.match(member.__name__)) ######################################################################## def makeOperations(moduleName): for operation in inspect.getmembers(sys.modules[moduleName], _isOperation): operation[1]() ######################################################################## makeOperations(__name__)