The modifications to ConfigState for AddInstanceDisk and AttachInstanceDisk are now done in WConfd daemon instead of using ConfigSync to do the modification from python codebase.
This eliminates _UnlockedAddInstanceDisk, and _UnlockedAttachInstanceDisk, but introduces them in the python tests to simulate the behaviour of AddInstanceDisk and AttachInstanceDisk without making a call to WConfd. Signed-off-by: BSRK Aditya <[email protected]> --- lib/config/__init__.py | 87 +++---------------- src/Ganeti/WConfd/ConfigModifications.hs | 139 +++++++++++++++++++++++++++--- test/py/ganeti.config_unittest.py | 13 --- test/py/testutils/config_mock.py | 24 ++++++ 4 files changed, 162 insertions(+), 101 deletions(-) diff --git a/lib/config/__init__.py b/lib/config/__init__.py index 5633b54..2f471d9 100644 --- a/lib/config/__init__.py +++ b/lib/config/__init__.py @@ -313,88 +313,21 @@ class ConfigWriter(object): """ return self._UnlockedGetInstanceDisks(inst_uuid) - def _UnlockedAddDisk(self, disk, replace=False): - """Add a disk to the config. - - @type disk: L{objects.Disk} - @param disk: The disk object - - """ + def AddInstanceDisk(self, inst_uuid, disk, idx=None, replace=False): + """Add a disk to the config and attach it to instance.""" if not isinstance(disk, objects.Disk): - raise errors.ProgrammerError("Invalid type passed to _UnlockedAddDisk") - - logging.info("Adding disk %s to configuration", disk.uuid) + raise errors.ProgrammerError("Invalid type passed to AddInstanceDisk") - if replace: - self._CheckUUIDpresent(disk) - else: - self._CheckUniqueUUID(disk, include_temporary=False) - disk.serial_no = 1 - disk.ctime = disk.mtime = time.time() disk.UpgradeConfig() - self._ConfigData().disks[disk.uuid] = disk - self._ConfigData().cluster.serial_no += 1 - self._UnlockedReleaseDRBDMinors(disk.uuid) - - def _UnlockedAttachInstanceDisk(self, inst_uuid, disk_uuid, idx=None): - """Attach a disk to an instance. - - @type inst_uuid: string - @param inst_uuid: The UUID of the instance object - @type disk_uuid: string - @param disk_uuid: The UUID of the disk object - @type idx: int - @param idx: the index of the newly attached disk; if not - passed, the disk will be attached as the last one. - - """ - instance = self._UnlockedGetInstanceInfo(inst_uuid) - if instance is None: - raise errors.ConfigurationError("Instance %s doesn't exist" - % inst_uuid) - if disk_uuid not in self._ConfigData().disks: - raise errors.ConfigurationError("Disk %s doesn't exist" % disk_uuid) - - if idx is None: - idx = len(instance.disks) - else: - if idx < 0: - raise IndexError("Not accepting negative indices other than -1") - elif idx > len(instance.disks): - raise IndexError("Got disk index %s, but there are only %s" % - (idx, len(instance.disks))) - - # Disk must not be attached anywhere else - for inst in self._ConfigData().instances.values(): - if disk_uuid in inst.disks: - raise errors.ReservationError("Disk %s already attached to instance %s" - % (disk_uuid, inst.name)) - - instance.disks.insert(idx, disk_uuid) - instance_disks = self._UnlockedGetInstanceDisks(inst_uuid) - _UpdateIvNames(idx, instance_disks[idx:]) - instance.serial_no += 1 - instance.mtime = time.time() - - @ConfigSync() - def AddInstanceDisk(self, inst_uuid, disk, idx=None, replace=False): - """Add a disk to the config and attach it to instance. - - This is a simple wrapper over L{_UnlockedAddDisk} and - L{_UnlockedAttachInstanceDisk}. - - """ - self._UnlockedAddDisk(disk, replace=replace) - self._UnlockedAttachInstanceDisk(inst_uuid, disk.uuid, idx) + utils.SimpleRetry(True, self._wconfd.AddInstanceDisk, 0.1, 30, + args=[inst_uuid, disk.ToDict(), idx, replace]) + self.OutDate() - @ConfigSync() def AttachInstanceDisk(self, inst_uuid, disk_uuid, idx=None): - """Attach an existing disk to an instance. - - This is a simple wrapper over L{_UnlockedAttachInstanceDisk}. - - """ - self._UnlockedAttachInstanceDisk(inst_uuid, disk_uuid, idx) + """Attach an existing disk to an instance.""" + utils.SimpleRetry(True, self._wconfd.AttachInstanceDisk, 0.1, 30, + args=[inst_uuid, disk_uuid, idx]) + self.OutDate() def _UnlockedDetachInstanceDisk(self, inst_uuid, disk_uuid): """Detach a disk from an instance. diff --git a/src/Ganeti/WConfd/ConfigModifications.hs b/src/Ganeti/WConfd/ConfigModifications.hs index c51b4b1..55fdd5f 100644 --- a/src/Ganeti/WConfd/ConfigModifications.hs +++ b/src/Ganeti/WConfd/ConfigModifications.hs @@ -39,28 +39,43 @@ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. module Ganeti.WConfd.ConfigModifications where -import Control.Lens.Setter ((.~)) +import Control.Lens.Setter ((.~), (%~)) import Control.Lens.Traversal (mapMOf) -import Control.Monad (unless) +import Control.Monad (unless, when, forM_) import Control.Monad.IO.Class (liftIO) -import Data.Maybe (isJust, maybeToList) +import Data.Maybe (isJust, maybeToList, fromMaybe) import Language.Haskell.TH (Name) -import System.Time (getClockTime) +import System.Time (getClockTime, ClockTime) import Text.Printf (printf) import qualified Data.Map as M import qualified Data.Set as S -import Ganeti.BasicTypes (GenericResult(..), toError) +import Ganeti.BasicTypes (GenericResult(..), genericResult, toError) import Ganeti.Errors (GanetiException(..)) -import Ganeti.JSON (GenericContainer(..), alterContainerL) +import Ganeti.JSON (Container, GenericContainer(..), alterContainerL + , lookupContainer, MaybeForJSON(..)) import Ganeti.Locking.Locks (ClientId, ciIdentifier) -import Ganeti.Logging.Lifted (logDebug) +import Ganeti.Logging.Lifted (logDebug, logInfo) import Ganeti.Objects import Ganeti.Objects.Lens import Ganeti.WConfd.ConfigState (ConfigState, csConfigData, csConfigDataL) import Ganeti.WConfd.Monad (WConfdMonad, modifyConfigWithLock) import qualified Ganeti.WConfd.TempRes as T +type DiskUUID = String +type InstanceUUID = String + +-- * accessor functions + +getInstanceByUUID :: ConfigState + -> InstanceUUID + -> GenericResult GanetiException Instance +getInstanceByUUID cs uuid = lookupContainer + (Bad . ConfigurationError $ + printf "Could not find instance with UUID %s" uuid) + uuid + (configInstances . csConfigData $ cs) + -- * getters -- | Gets all logical volumes in the cluster @@ -148,16 +163,85 @@ addInstanceChecks inst replace cs = do (show $ instName inst) (show macsInUse) if replace then do - let check = checkUUIDpresent cs inst - unless check . Bad . ConfigurationError $ printf + unless (checkUUIDpresent cs inst) . Bad . ConfigurationError $ printf "Cannot add %s: UUID %s already in use" (show $ instName inst) (instUuid inst) else do - let check = checkUniqueUUID cs inst - unless check . Bad . ConfigurationError $ printf + unless (checkUniqueUUID cs inst) . Bad . ConfigurationError $ printf "Cannot replace %s: UUID %s not present" (show $ instName inst) (instUuid inst) +addDiskChecks :: Disk + -> Bool + -> ConfigState + -> GenericResult GanetiException () +addDiskChecks disk replace cs = + if replace + then do + unless (checkUUIDpresent cs disk) . Bad . ConfigurationError $ printf + "Cannot add %s: UUID %s already in use" + (show $ diskName disk) (diskUuid disk) + else do + unless (checkUniqueUUID cs disk) . Bad . ConfigurationError $ printf + "Cannot replace %s: UUID %s not present" + (show $ diskName disk) (diskUuid disk) + +attachInstanceDiskChecks :: InstanceUUID + -> DiskUUID + -> MaybeForJSON Int + -> ConfigState + -> GenericResult GanetiException () +attachInstanceDiskChecks uuidInst uuidDisk idx' cs = do + let diskPresent = elem uuidDisk . map diskUuid . M.elems + . fromContainer . configDisks . csConfigData $ cs + unless diskPresent . Bad . ConfigurationError $ printf + "Disk %s doesn't exist" uuidDisk + + inst <- getInstanceByUUID cs uuidInst + let numDisks = length $ instDisks inst + idx = fromMaybe numDisks (unMaybeForJSON idx') + + when (idx < 0) . Bad . GenericError $ + "Not accepting negative indices" + when (idx > numDisks) . Bad . GenericError $ printf + "Got disk index %d, but there are only %d" idx numDisks + + let insts = M.elems . fromContainer . configInstances . csConfigData $ cs + forM_ insts (\inst' -> when (uuidDisk `elem` instDisks inst') . Bad + . ReservationError $ printf "Disk %s already attached to instance %s" + uuidDisk (show $ instName inst)) + +-- * Pure config modifications functions + +attachInstanceDisk' :: InstanceUUID + -> DiskUUID + -> MaybeForJSON Int + -> ClockTime + -> ConfigState + -> ConfigState +attachInstanceDisk' iUuid dUuid idx' ct cs = + let inst = genericResult (error "impossible") id (getInstanceByUUID cs iUuid) + numDisks = length $ instDisks inst + idx = fromMaybe numDisks (unMaybeForJSON idx') + + insert = instDisksL %~ (\ds -> take idx ds ++ [dUuid] ++ drop idx ds) + incr = instSerialL %~ (+ 1) + time = instMtimeL .~ ct + + inst' = time . incr . insert $ inst + disks = updateIvNames idx inst' (configDisks . csConfigData $ cs) + + ri = csConfigDataL . configInstancesL + . alterContainerL iUuid .~ Just inst' + rds = csConfigDataL . configDisksL .~ disks + in rds . ri $ cs + where updateIvNames :: Int -> Instance -> Container Disk -> Container Disk + updateIvNames idx inst (GenericContainer m) = + let dUuids = drop idx (instDisks inst) + upgradeIv m' (idx'', dUuid') = + M.adjust (diskIvNameL .~ "disk/" ++ show idx'') dUuid' m' + in GenericContainer $ foldl upgradeIv m (zip [idx..] dUuids) + -- * RPCs -- | Add a new instance to the configuration, release DRBD minors, @@ -183,8 +267,41 @@ addInstance inst cid replace = do logDebug $ "AddInstance: result of config modification is " ++ show r return $ isJust r +addInstanceDisk :: InstanceUUID + -> Disk + -> MaybeForJSON Int + -> Bool + -> WConfdMonad Bool +addInstanceDisk iUuid disk idx replace = do + logInfo $ printf "Adding disk %s to configuration" (diskUuid disk) + ct <- liftIO getClockTime + let addD = csConfigDataL . configDisksL . alterContainerL (uuidOf disk) + .~ Just disk + incrSerialNo = csConfigDataL . configSerialL %~ (+1) + r <- modifyConfigWithLock (\_ cs -> do + toError $ addDiskChecks disk replace cs + let cs' = incrSerialNo . addD $ cs + toError $ attachInstanceDiskChecks iUuid (diskUuid disk) idx cs' + return $ attachInstanceDisk' iUuid (diskUuid disk) idx ct cs') + . T.releaseDRBDMinors $ uuidOf disk + return $ isJust r + +attachInstanceDisk :: InstanceUUID + -> DiskUUID + -> MaybeForJSON Int + -> WConfdMonad Bool +attachInstanceDisk iUuid dUuid idx = do + ct <- liftIO getClockTime + r <- modifyConfigWithLock (\_ cs -> do + toError $ attachInstanceDiskChecks iUuid dUuid idx cs + return $ attachInstanceDisk' iUuid dUuid idx ct cs) + (return ()) + return $ isJust r + -- * The list of functions exported to RPC. exportedFunctions :: [Name] exportedFunctions = [ 'addInstance + , 'addInstanceDisk + , 'attachInstanceDisk ] diff --git a/test/py/ganeti.config_unittest.py b/test/py/ganeti.config_unittest.py index 47750fe..e56c723 100755 --- a/test/py/ganeti.config_unittest.py +++ b/test/py/ganeti.config_unittest.py @@ -707,24 +707,11 @@ class TestConfigRunner(unittest.TestCase): self.assertRaises(errors.ProgrammerError, cfg.DetachInstanceDisk, "test-uuid", "disk0") - # Attach disk to non-existent instance - self.assertRaises(errors.ConfigurationError, cfg.AttachInstanceDisk, - "1134", "disk0") - - # Attach non-existent disk - self.assertRaises(errors.ConfigurationError, cfg.AttachInstanceDisk, - "test-uuid", "disk1") - # Attach disk cfg.AttachInstanceDisk("test-uuid", "disk0") instance_disks = cfg.GetInstanceDisks("test-uuid") self.assertEqual(instance_disks, [disk]) - # Attach disk again - self.assertRaises(errors.ReservationError, cfg.AttachInstanceDisk, - "test-uuid", "disk0") - - def _IsErrorInList(err_str, err_list): return any(map(lambda e: err_str in e, err_list)) diff --git a/test/py/testutils/config_mock.py b/test/py/testutils/config_mock.py index ed78e63..0cc4eda 100644 --- a/test/py/testutils/config_mock.py +++ b/test/py/testutils/config_mock.py @@ -859,3 +859,27 @@ class ConfigMock(config.ConfigWriter): self._ConfigData().cluster.serial_no += 1 # pylint: disable=E1103 self._UnlockedReleaseDRBDMinors(instance.uuid) self._UnlockedCommitTemporaryIps(ec_id) + + def _UnlockedAddDisk(self, disk): + disk.UpgradeConfig() + self._ConfigData().disks[disk.uuid] = disk + self._ConfigData().cluster.serial_no += 1 + self._UnlockedReleaseDRBDMinors(disk.uuid) + + def _UnlockedAttachInstanceDisk(self, inst_uuid, disk_uuid, idx=None): + instance = self._UnlockedGetInstanceInfo(inst_uuid) + if idx is None: + idx = len(instance.disks) + instance.disks.insert(idx, disk_uuid) + instance_disks = self._UnlockedGetInstanceDisks(inst_uuid) + for (disk_idx, disk) in enumerate(instance_disks[idx:]): + disk.iv_name = "disk/%s" % (idx + disk_idx) + instance.serial_no += 1 + instance.mtime = time.time() + + def AddInstanceDisk(self, inst_uuid, disk, idx=None, replace=False): + self._UnlockedAddDisk(disk) + self._UnlockedAttachInstanceDisk(inst_uuid, disk.uuid, idx) + + def AttachInstanceDisk(self, inst_uuid, disk_uuid, idx=None): + self._UnlockedAttachInstanceDisk(inst_uuid, disk_uuid, idx) -- 1.7.10.4
