This commit adds the feature requested in Issue 738. The reporter asked for the ability to create an instance to a specific group.
Signed-off-by: BSRK Aditya <[email protected]> --- lib/cli.py | 2 + lib/cmdlib/instance.py | 1 + lib/cmdlib/instance_storage.py | 1 + lib/cmdlib/test.py | 2 + lib/masterd/iallocator.py | 2 + man/gnt-instance.rst | 5 ++- src/Ganeti/HTools/Backend/IAlloc.hs | 13 +++++-- src/Ganeti/HTools/Cluster.hs | 78 ++++++++++++++++++++++++++++++------- src/Ganeti/HTools/Loader.hs | 13 ++++--- src/Ganeti/OpCodes.hs | 2 + test/hs/Test/Ganeti/OpCodes.hs | 4 +- test/py/cmdlib/test_unittest.py | 2 + 12 files changed, 99 insertions(+), 26 deletions(-) diff --git a/lib/cli.py b/lib/cli.py index 1f5d909..937c69e 100644 --- a/lib/cli.py +++ b/lib/cli.py @@ -1824,6 +1824,7 @@ COMMON_CREATE_OPTS = [ IALLOCATOR_OPT, NET_OPT, NODE_PLACEMENT_OPT, + NODEGROUP_OPT, NOIPCHECK_OPT, NOCONFLICTSCHECK_OPT, NONAMECHECK_OPT, @@ -2871,6 +2872,7 @@ def GenericInstanceCreate(mode, opts, args): op = opcodes.OpInstanceCreate(instance_name=instance, disks=disks, disk_template=opts.disk_template, + group_name=opts.nodegroup, nics=nics, conflicts_check=opts.conflicts_check, pnode=pnode, snode=snode, diff --git a/lib/cmdlib/instance.py b/lib/cmdlib/instance.py index 5944955..06233e8 100644 --- a/lib/cmdlib/instance.py +++ b/lib/cmdlib/instance.py @@ -125,6 +125,7 @@ def _CreateInstanceAllocRequest(op, disks, nics, beparams, node_name_whitelist): spindle_use = beparams[constants.BE_SPINDLE_USE] return iallocator.IAReqInstanceAlloc(name=op.instance_name, disk_template=op.disk_template, + group_name=op.group_name, tags=op.tags, os=op.os_type, vcpus=beparams[constants.BE_VCPUS], diff --git a/lib/cmdlib/instance_storage.py b/lib/cmdlib/instance_storage.py index 53dd4e0..ac5f10b 100644 --- a/lib/cmdlib/instance_storage.py +++ b/lib/cmdlib/instance_storage.py @@ -621,6 +621,7 @@ class LUInstanceRecreateDisks(LogicalUnit): } for d in self.cfg.GetInstanceDisks(self.instance.uuid)] req = iallocator.IAReqInstanceAlloc(name=self.op.instance_name, disk_template=disk_template, + group_name=None, tags=list(self.instance.GetTags()), os=self.instance.os, nics=[{}], diff --git a/lib/cmdlib/test.py b/lib/cmdlib/test.py index 422848b..fbc467a 100644 --- a/lib/cmdlib/test.py +++ b/lib/cmdlib/test.py @@ -400,6 +400,7 @@ class LUTestAllocator(NoHooksLU): memory=self.op.memory, disks=self.op.disks, disk_template=self.op.disk_template, + group_name=self.op.group_name, os=self.op.os, tags=self.op.tags, nics=self.op.nics, @@ -423,6 +424,7 @@ class LUTestAllocator(NoHooksLU): memory=self.op.memory, disks=self.op.disks, disk_template=disk_template, + group_name=self.op.group_name, os=self.op.os, tags=self.op.tags, nics=self.op.nics, diff --git a/lib/masterd/iallocator.py b/lib/masterd/iallocator.py index c977eb9..bc8a7c4 100644 --- a/lib/masterd/iallocator.py +++ b/lib/masterd/iallocator.py @@ -158,6 +158,7 @@ class IAReqInstanceAlloc(IARequestBase): ("spindle_use", ht.TNonNegativeInt), ("disks", ht.TListOf(ht.TDict)), ("disk_template", ht.TString), + ("group_name", ht.TMaybe(ht.TNonEmptyString)), ("os", ht.TString), ("tags", _STRING_LIST), ("nics", ht.TListOf(ht.TDict)), @@ -188,6 +189,7 @@ class IAReqInstanceAlloc(IARequestBase): return { "name": self.name, "disk_template": self.disk_template, + "group_name": self.group_name, "tags": self.tags, "os": self.os, "vcpus": self.vcpus, diff --git a/man/gnt-instance.rst b/man/gnt-instance.rst index 020192e..16c1b6b 100644 --- a/man/gnt-instance.rst +++ b/man/gnt-instance.rst @@ -40,7 +40,7 @@ ADD | [--os-parameters-private *param*=*value*... ] | [--os-parameters-secret *param*=*value*... ] | [\--file-storage-dir *dir\_path*] [\--file-driver {loop \| blktap \| blktap2}] -| {{-n|\--node} *node[:secondary-node]* \| {-I|\--iallocator} *name*} +| {{-n|\--node} *node[:secondary-node]* \| {-I|\--iallocator} *name* \| {-g|--node-group} *nodegroup*} | {{-o|\--os-type} *os-type*} | [\--submit] [\--print-job-id] | [\--ignore-ipolicy] @@ -874,6 +874,9 @@ the allocator will select nodes for this instance automatically, so you don't need to pass them with the ``-n`` option. For more information please refer to the instance allocator documentation. +The ``-g (--node-group)`` option can be used to create the instance +in a particular node group, specified by name. + The ``-t (--disk-template)`` options specifies the disk layout type for the instance. If no disk template is specified, the default disk template is used. The default disk template is the first in the list diff --git a/src/Ganeti/HTools/Backend/IAlloc.hs b/src/Ganeti/HTools/Backend/IAlloc.hs index 866efe7..dc52eb7 100644 --- a/src/Ganeti/HTools/Backend/IAlloc.hs +++ b/src/Ganeti/HTools/Backend/IAlloc.hs @@ -211,10 +211,11 @@ parseData now body = do _ | optype == C.iallocatorModeAlloc -> do rname <- extrReq "name" + rgn <- maybeFromObj request "group_name" req_nodes <- extrReq "required_nodes" inew <- parseBaseInstance rname request let io = updateExclTags (extractExTags ctags) $ snd inew - return $ Allocate io req_nodes + return $ Allocate io (Cluster.AllocDetails req_nodes rgn) | optype == C.iallocatorModeReloc -> do rname <- extrReq "name" @@ -243,14 +244,16 @@ parseData now body = do do arry <- extrReq "instances" :: Result [JSObject JSValue] let inst_reqs = map fromJSObject arry - prqs <- mapM (\r -> + prqs <- forM inst_reqs (\r -> do rname <- extrFromReq r "name" + rgn <- maybeFromObj request "group_name" req_nodes <- extrFromReq r "required_nodes" inew <- parseBaseInstance rname r let io = updateExclTags (extractExTags ctags) $ snd inew - return (io, req_nodes)) inst_reqs + return (io, Cluster.AllocDetails + req_nodes rgn)) return $ MultiAllocate prqs | otherwise -> fail ("Invalid request type '" ++ optype ++ "'") return (msgs, Request rqtype cdata) @@ -399,8 +402,10 @@ processRequest :: Request -> Result IAllocResult processRequest request = let Request rqtype (ClusterData gl nl il _ _) = request in case rqtype of - Allocate xi reqn -> + Allocate xi (Cluster.AllocDetails reqn Nothing) -> Cluster.tryMGAlloc gl nl il xi reqn >>= formatAllocate il + Allocate xi (Cluster.AllocDetails reqn (Just gn)) -> + Cluster.tryGroupAlloc gl nl il gn xi reqn >>= formatAllocate il Relocate idx reqn exnodes -> processRelocate gl nl il idx reqn exnodes >>= formatRelocate ChangeGroup gdxs idxs -> diff --git a/src/Ganeti/HTools/Cluster.hs b/src/Ganeti/HTools/Cluster.hs index 022f6d3..3116e87 100644 --- a/src/Ganeti/HTools/Cluster.hs +++ b/src/Ganeti/HTools/Cluster.hs @@ -29,7 +29,8 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA module Ganeti.HTools.Cluster ( -- * Types - AllocSolution(..) + AllocDetails(..) + , AllocSolution(..) , EvacSolution(..) , Table(..) , CStats(..) @@ -63,6 +64,7 @@ module Ganeti.HTools.Cluster -- * IAllocator functions , genAllocNodes , tryAlloc + , tryGroupAlloc , tryMGAlloc , tryNodeEvac , tryChangeGroup @@ -77,8 +79,9 @@ module Ganeti.HTools.Cluster , splitCluster ) where -import Control.Applicative (liftA2) +import Control.Applicative ((<$>), liftA2) import Control.Arrow ((&&&)) +import Control.Monad (unless) import qualified Data.IntSet as IntSet import Data.List import Data.Maybe (fromJust, fromMaybe, isJust, isNothing) @@ -99,6 +102,12 @@ import Ganeti.Types (EvacMode(..), mkNonEmpty) -- * Types +-- | Allocation details for an instance, specifying +-- | required number of nodes, and +-- | an optional group (name) to allocate to +data AllocDetails = AllocDetails Int (Maybe String) + deriving (Show) + -- | Allocation\/relocation solution. data AllocSolution = AllocSolution { asFailures :: [FailMode] -- ^ Failure counts @@ -842,6 +851,12 @@ sortMGResults sols = (extractScore . fromJust . asSolution) sol) in sortBy (comparing solScore) sols +-- | Determines if a group is connected to the networks required by the +-- | instance. +hasRequiredNetworks :: Group.Group -> Instance.Instance -> Bool +hasRequiredNetworks ng = all hasNetwork . Instance.nics + where hasNetwork = maybe True (`elem` Group.networks ng) . Nic.network + -- | Removes node groups which can't accommodate the instance filterValidGroups :: [(Group.Group, (Node.List, Instance.List))] -> Instance.Instance @@ -849,17 +864,32 @@ filterValidGroups :: [(Group.Group, (Node.List, Instance.List))] filterValidGroups [] _ = ([], []) filterValidGroups (ng:ngs) inst = let (valid_ngs, msgs) = filterValidGroups ngs inst - hasNetwork nic = case Nic.network nic of - Just net -> net `elem` Group.networks (fst ng) - Nothing -> True - hasRequiredNetworks = all hasNetwork (Instance.nics inst) - in if hasRequiredNetworks + in if hasRequiredNetworks (fst ng) inst then (ng:valid_ngs, msgs) else (valid_ngs, ("group " ++ Group.name (fst ng) ++ " is not connected to a network required by instance " ++ Instance.name inst):msgs) +-- | Finds an allocation solution for an instance on a group +findAllocation :: Group.List -- ^ The group list + -> Node.List -- ^ The node list + -> Instance.List -- ^ The instance list + -> Gdx -- ^ The group to allocate to + -> Instance.Instance -- ^ The instance to allocate + -> Int -- ^ Required number of nodes + -> Result (AllocSolution, [String]) +findAllocation mggl mgnl mgil gdx inst cnt = do + let belongsTo nl' nidx = nidx `elem` map Node.idx (Container.elems nl') + nl = Container.filter ((== gdx) . Node.group) mgnl + il = Container.filter (belongsTo nl . Instance.pNode) mgil + group' = Container.find gdx mggl + unless (hasRequiredNetworks group' inst) . failError + $ "The group " ++ Group.name group' ++ " is not connected to\ + \ a network required by instance " ++ Instance.name inst + solution <- genAllocNodes mggl nl cnt False >>= tryAlloc nl il inst + return (solution, solutionDescription (group', return solution)) + -- | Finds the best group for an instance on a multi-group cluster. -- -- Only solutions in @preferred@ and @last_resort@ groups will be @@ -909,6 +939,19 @@ tryMGAlloc mggl mgnl mgil inst cnt = do selmsg = "Selected group: " ++ group_name return $ solution { asLog = selmsg:all_msgs } +-- | Try to allocate an instance to a group. +tryGroupAlloc :: Group.List -- ^ The group list + -> Node.List -- ^ The node list + -> Instance.List -- ^ The instance list + -> String -- ^ The allocation group (name) + -> Instance.Instance -- ^ The instance to allocate + -> Int -- ^ Required number of nodes + -> Result AllocSolution -- ^ Solution +tryGroupAlloc mggl mgnl ngil gn inst cnt = do + gdx <- Group.idx <$> Container.findByName mggl gn + (solution, msgs) <- findAllocation mggl mgnl ngil gdx inst cnt + return $ solution { asLog = msgs } + -- | Calculate the new instance list after allocation solution. updateIl :: Instance.List -- ^ The original instance list -> Maybe Node.AllocElement -- ^ The result of the allocation attempt @@ -924,16 +967,21 @@ extractNl nl Nothing = nl extractNl _ (Just (xnl, _, _, _)) = xnl -- | Try to allocate a list of instances on a multi-group cluster. -allocList :: Group.List -- ^ The group list - -> Node.List -- ^ The node list - -> Instance.List -- ^ The instance list - -> [(Instance.Instance, Int)] -- ^ The instance to allocate - -> AllocSolutionList -- ^ Possible solution list +allocList :: Group.List -- ^ The group list + -> Node.List -- ^ The node list + -> Instance.List -- ^ The instance list + -> [(Instance.Instance, AllocDetails)] -- ^ The instance to + -- allocate + -> AllocSolutionList -- ^ Possible solution + -- list -> Result (Node.List, Instance.List, - AllocSolutionList) -- ^ The final solution list + AllocSolutionList) -- ^ The final solution + -- list allocList _ nl il [] result = Ok (nl, il, result) -allocList gl nl il ((xi, xicnt):xies) result = do - ares <- tryMGAlloc gl nl il xi xicnt +allocList gl nl il ((xi, AllocDetails xicnt mgn):xies) result = do + ares <- case mgn of + Nothing -> tryMGAlloc gl nl il xi xicnt + Just gn -> tryGroupAlloc gl nl il gn xi xicnt let sol = asSolution ares nl' = extractNl nl sol il' = updateIl il sol diff --git a/src/Ganeti/HTools/Loader.hs b/src/Ganeti/HTools/Loader.hs index 7440ba3..60aa361 100644 --- a/src/Ganeti/HTools/Loader.hs +++ b/src/Ganeti/HTools/Loader.hs @@ -79,11 +79,14 @@ request-specific fields. -} data RqType - = Allocate Instance.Instance Int -- ^ A new instance allocation - | Relocate Idx Int [Ndx] -- ^ Choose a new secondary node - | NodeEvacuate [Idx] EvacMode -- ^ node-evacuate mode - | ChangeGroup [Gdx] [Idx] -- ^ Multi-relocate mode - | MultiAllocate [(Instance.Instance, Int)] -- ^ Multi-allocate mode + = Allocate Instance.Instance Cluster.AllocDetails -- ^ A new instance + -- allocation + | Relocate Idx Int [Ndx] -- ^ Choose a new + -- secondary node + | NodeEvacuate [Idx] EvacMode -- ^ node-evacuate mode + | ChangeGroup [Gdx] [Idx] -- ^ Multi-relocate mode + | MultiAllocate [(Instance.Instance, Cluster.AllocDetails)] + -- ^ Multi-allocate mode deriving (Show) -- | A complete request, as received from Ganeti. diff --git a/src/Ganeti/OpCodes.hs b/src/Ganeti/OpCodes.hs index d5b3e01..14adef9 100644 --- a/src/Ganeti/OpCodes.hs +++ b/src/Ganeti/OpCodes.hs @@ -430,6 +430,7 @@ $(genOpCode "OpCode" , pInstBeParams , pInstDisks , pOptDiskTemplate + , pOptGroupName , pFileDriver , pFileStorageDir , pInstHvParams @@ -857,6 +858,7 @@ $(genOpCode "OpCode" , pTargetGroups , pIAllocatorSpindleUse , pIAllocatorCount + , pOptGroupName ], "iallocator") , ("OpTestJqueue", diff --git a/test/hs/Test/Ganeti/OpCodes.hs b/test/hs/Test/Ganeti/OpCodes.hs index 4db01f2..121ac27 100644 --- a/test/hs/Test/Ganeti/OpCodes.hs +++ b/test/hs/Test/Ganeti/OpCodes.hs @@ -270,6 +270,7 @@ instance Arbitrary OpCodes.OpCode where <*> pure emptyJSObject -- beparams <*> arbitrary -- disks <*> arbitrary -- disk_template + <*> genMaybe genNameNE -- group_name <*> arbitrary -- file_driver <*> genMaybe genNameNE -- file_storage_dir <*> pure emptyJSObject -- hvparams @@ -416,7 +417,8 @@ instance Arbitrary OpCodes.OpCode where (genTags >>= mapM mkNonEmpty) <*> arbitrary <*> arbitrary <*> genMaybe genNameNE <*> arbitrary <*> genMaybe genNodeNamesNE <*> arbitrary <*> - genMaybe genNamesNE <*> arbitrary <*> arbitrary + genMaybe genNamesNE <*> arbitrary <*> arbitrary <*> + genMaybe genNameNE "OP_TEST_JQUEUE" -> OpCodes.OpTestJqueue <$> arbitrary <*> arbitrary <*> resize 20 (listOf genFQDN) <*> arbitrary diff --git a/test/py/cmdlib/test_unittest.py b/test/py/cmdlib/test_unittest.py index 47fc761..bd2d68e 100644 --- a/test/py/cmdlib/test_unittest.py +++ b/test/py/cmdlib/test_unittest.py @@ -126,6 +126,7 @@ class TestLUTestAllocator(CmdlibTestCase): memory=0, disk_template=constants.DT_DISKLESS, os="mock_os", + group_name="default", vcpus=1) self.valid_multi_alloc_op = \ self.CopyOpCode(self.base_op, @@ -134,6 +135,7 @@ class TestLUTestAllocator(CmdlibTestCase): memory=0, disk_template=constants.DT_DISKLESS, os="mock_os", + group_name="default", vcpus=1) self.valid_reloc_op = \ self.CopyOpCode(self.base_op, -- 1.9.1.423.g4596e3a
