AddInstance relies upon _ConfigData() for some validations. However, _ConfigData() cannot be relied upon without @ConfigSync. This patch ports these checks to the wconfd daemon.
Signed-off-by: BSRK Aditya <[email protected]> --- lib/config/__init__.py | 17 +--- src/Ganeti/WConfd/ConfigModifications.hs | 130 +++++++++++++++++++++++++++--- 2 files changed, 121 insertions(+), 26 deletions(-) diff --git a/lib/config/__init__.py b/lib/config/__init__.py index e00573a..ec1ce51 100644 --- a/lib/config/__init__.py +++ b/lib/config/__init__.py @@ -1772,23 +1772,12 @@ class ConfigWriter(object): if not isinstance(instance, objects.Instance): raise errors.ProgrammerError("Invalid type passed to AddInstance") - all_macs = self._AllMACs() - for nic in instance.nics: - if nic.mac in all_macs: - raise errors.ConfigurationError("Cannot add instance %s:" - " MAC address '%s' already in use." % - (instance.name, nic.mac)) - - if replace: - self._CheckUUIDpresent(instance) - else: - self._CheckUniqueUUID(instance, include_temporary=False) - instance.serial_no = 1 - instance.ctime = instance.mtime = time.time() utils.SimpleRetry(True, self._wconfd.AddInstance, 0.1, 30, - args=[instance.ToDict(), self._GetWConfdContext()]) + args=[instance.ToDict(), + self._GetWConfdContext(), + replace]) self.OutDate() def _EnsureUUID(self, item, ec_id): diff --git a/src/Ganeti/WConfd/ConfigModifications.hs b/src/Ganeti/WConfd/ConfigModifications.hs index aa11b2a..e318eb4 100644 --- a/src/Ganeti/WConfd/ConfigModifications.hs +++ b/src/Ganeti/WConfd/ConfigModifications.hs @@ -41,37 +41,143 @@ module Ganeti.WConfd.ConfigModifications where import Control.Lens.Setter ((.~)) import Control.Lens.Traversal (mapMOf) -import Data.Maybe (isJust) +import Control.Monad (unless) +import Control.Monad.IO.Class (liftIO) +import Data.List (nub, intersect) +import Data.Maybe (isJust, maybeToList) import Language.Haskell.TH (Name) +import System.Time (getClockTime) +import Text.Printf (printf) +import qualified Data.Map as M -import Ganeti.JSON (alterContainerL) +import Ganeti.BasicTypes (GenericResult(..), toError) +import Ganeti.Errors (GanetiException(..)) +import Ganeti.JSON (GenericContainer(..), alterContainerL) import Ganeti.Locking.Locks (ClientId, ciIdentifier) import Ganeti.Logging.Lifted (logDebug) import Ganeti.Objects import Ganeti.Objects.Lens -import Ganeti.WConfd.ConfigState (csConfigDataL) +import Ganeti.WConfd.ConfigState (ConfigState, csConfigData, csConfigDataL) import Ganeti.WConfd.Monad (WConfdMonad, modifyConfigWithLock) import qualified Ganeti.WConfd.TempRes as T +-- * getters + +-- | Gets all logical volumes in the cluster +getAllLVs :: ConfigState -> [String] +getAllLVs = concatMap getLVsOfDisk . M.elems + . fromContainer . configDisks . csConfigData + where convert (LogicalVolume lvG lvV) = lvG ++ "/" ++ lvV + getDiskLV :: Disk -> Maybe String + getDiskLV disk = case diskLogicalId disk of + Just (LIDPlain lv) -> Just (convert lv) + _ -> Nothing + getLVsOfDisk :: Disk -> [String] + getLVsOfDisk disk = maybeToList (getDiskLV disk) + ++ concatMap getLVsOfDisk (diskChildren disk) + +-- | Gets the ids of nodes, instances, node groups, +-- networks, disks, nics, and the custer itself. +getAllIDs :: ConfigState -> [String] +getAllIDs cs = + let lvs = getAllLVs cs + keysFromC :: GenericContainer a b -> [a] + keysFromC = M.keys . fromContainer + + valuesFromC :: GenericContainer a b -> [b] + valuesFromC = M.elems . fromContainer + + instKeys = keysFromC . configInstances . csConfigData $ cs + nodeKeys = keysFromC . configNodes . csConfigData $ cs + + instValues = map uuidOf . valuesFromC + . configInstances . csConfigData $ cs + nodeValues = map uuidOf . valuesFromC . configNodes . csConfigData $ cs + nodeGroupValues = map uuidOf . valuesFromC + . configNodegroups . csConfigData $ cs + networkValues = map uuidOf . valuesFromC + . configNetworks . csConfigData $ cs + disksValues = map uuidOf . valuesFromC . configDisks . csConfigData $ cs + + nics = map nicUuid . concatMap instNics + . valuesFromC . configInstances . csConfigData $ cs + + cluster = uuidOf . configCluster . csConfigData $ cs + in nub $ lvs ++ instKeys ++ nodeKeys ++ instValues ++ nodeValues + ++ nodeGroupValues ++ networkValues ++ disksValues ++ nics ++ [cluster] + +getAllMACs :: ConfigState -> [String] +getAllMACs = map nicMac . concatMap instNics . M.elems + . fromContainer . configInstances . csConfigData + +-- * UUID config checks + +-- | Checks if the config has the given UUID +checkUUIDpresent :: UuidObject a + => ConfigState + -> a + -> Bool +checkUUIDpresent cs a = uuidOf a `elem` getAllIDs cs + +-- | Checks if the given UUID is new (i.e., no in the config) +checkUniqueUUID :: UuidObject a + => ConfigState + -> a + -> Bool +checkUniqueUUID cs a = not $ checkUUIDpresent cs a + +-- * RPC checks + +-- | Verifications done before adding an instance. +-- Currently confirms that the instance's macs are not +-- in use, and that the instance's UUID being +-- present (or not present) in the config based on +-- weather the instance is being replaced (or not). +-- +-- TODO: add more verifications to this call; +-- the client should have a lock on the name of the instance. +addInstanceChecks :: Instance + -> Bool + -> ConfigState + -> GenericResult GanetiException () +addInstanceChecks inst replace cs = do + let macsInUse = map nicMac (instNics inst) `intersect` getAllMACs cs + unless (null macsInUse) . Bad . ConfigurationError $ printf + "Cannot add instance %s; MAC addresses %s already in use" + (show $ instName inst) (show macsInUse) + if replace + then do + let check = checkUUIDpresent cs inst + unless check . 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 + "Cannot replace %s: UUID %s not present" + (show $ instName inst) (instUuid inst) + +-- * RPCs -- | Add a new instance to the configuration, release DRBD minors, -- and commit temporary IPs, all while temporarily holding the config -- lock. Return True upon success and False if the config lock was not -- available and the client should retry. --- --- TODO: add verifications to this call; the client should have a lock --- on the name of the instance, and no instance with the given uuid should --- exist. -addInstance :: Instance -> ClientId -> WConfdMonad Bool -addInstance inst cid = do +addInstance :: Instance -> ClientId -> Bool -> WConfdMonad Bool +addInstance inst cid replace = do + ct <- liftIO getClockTime logDebug $ "AddInstance: client " ++ show (ciIdentifier cid) ++ " adding instance " ++ uuidOf inst ++ " with name " ++ show (instName inst) - let addInst = csConfigDataL . configInstancesL . alterContainerL (uuidOf inst) - .~ Just inst + let setCtime = instCtimeL .~ ct + setMtime = instMtimeL .~ ct + addInst i = csConfigDataL . configInstancesL . alterContainerL (uuidOf i) + .~ Just i commitRes tr = mapMOf csConfigDataL $ T.commitReservedIps cid tr r <- modifyConfigWithLock - (\tr cs -> commitRes tr $ addInst cs) + (\tr cs -> do + toError $ addInstanceChecks inst replace cs + commitRes tr $ addInst (setMtime . setCtime $ inst) cs) . T.releaseDRBDMinors $ uuidOf inst logDebug $ "AddInstance: result of config modification is " ++ show r return $ isJust r -- 1.7.10.4
