From f94a9680a0c991de69db413c149143fbbb1669a0 Mon Sep 17 00:00:00 2001 From: Iustin Pop <iustin@google.com> Date: Mon, 10 Dec 2012 10:58:33 +0100 Subject: [PATCH] Implement support for QFF_SPLIT_TIMESTAMP I am very very unhappy with this patch. We have to do this, in order to be compatible with the Python code and behaviour, but there's lots of duplication; both the Python and the Haskell code will need to be cleaned up and simplified (we don't need QFF_* at all). For now though, this implements QffTimestamp and associated code. Signed-off-by: Iustin Pop <iustin@google.com> Reviewed-by: Guido Trotter <ultrotter@google.com> --- htest/Test/Ganeti/Query/Query.hs | 4 +-- htools/Ganeti/Query/Common.hs | 12 ++++---- htools/Ganeti/Query/Filter.hs | 21 +++++++++---- htools/Ganeti/Query/Group.hs | 29 ++++++++++-------- htools/Ganeti/Query/Node.hs | 52 ++++++++++++++++++-------------- htools/Ganeti/Query/Query.hs | 9 +++--- htools/Ganeti/Query/Types.hs | 11 ++++++- 7 files changed, 83 insertions(+), 55 deletions(-) diff --git a/htest/Test/Ganeti/Query/Query.hs b/htest/Test/Ganeti/Query/Query.hs index 9c50948e2..37ed56c34 100644 --- a/htest/Test/Ganeti/Query/Query.hs +++ b/htest/Test/Ganeti/Query/Query.hs @@ -156,7 +156,7 @@ case_queryNode_allfields = do Ok (QueryFieldsResult v) -> return v let field_sort = compare `on` fdefName assertEqual "Mismatch in all fields list" - (sortBy field_sort . map fst $ Map.elems nodeFieldsMap) + (sortBy field_sort . map (\(f, _, _) -> f) $ Map.elems nodeFieldsMap) (sortBy field_sort fdefs) -- * Same as above, but for group @@ -228,7 +228,7 @@ case_queryGroup_allfields = do Ok (QueryFieldsResult v) -> return v let field_sort = compare `on` fdefName assertEqual "Mismatch in all fields list" - (sortBy field_sort . map fst $ Map.elems groupFieldsMap) + (sortBy field_sort . map (\(f, _, _) -> f) $ Map.elems groupFieldsMap) (sortBy field_sort fdefs) diff --git a/htools/Ganeti/Query/Common.hs b/htools/Ganeti/Query/Common.hs index b9029bc28..67d1547b4 100644 --- a/htools/Ganeti/Query/Common.hs +++ b/htools/Ganeti/Query/Common.hs @@ -104,29 +104,29 @@ rpcErrorToStatus _ = RSNoData timeStampFields :: (TimeStampObject a) => FieldList a b timeStampFields = [ (FieldDefinition "ctime" "CTime" QFTTimestamp "Creation timestamp", - FieldSimple (rsNormal . cTimeOf)) + FieldSimple (rsNormal . cTimeOf), QffNormal) , (FieldDefinition "mtime" "MTime" QFTTimestamp "Modification timestamp", - FieldSimple (rsNormal . mTimeOf)) + FieldSimple (rsNormal . mTimeOf), QffNormal) ] -- | The list of UUID fields. uuidFields :: (UuidObject a) => String -> FieldList a b uuidFields name = [ (FieldDefinition "uuid" "UUID" QFTText (name ++ " UUID"), - FieldSimple (rsNormal . uuidOf)) ] + FieldSimple (rsNormal . uuidOf), QffNormal) ] -- | The list of serial number fields. serialFields :: (SerialNoObject a) => String -> FieldList a b serialFields name = [ (FieldDefinition "serial_no" "SerialNo" QFTNumber (name ++ " object serial number, incremented on each modification"), - FieldSimple (rsNormal . serialOf)) ] + FieldSimple (rsNormal . serialOf), QffNormal) ] -- | The list of tag fields. tagsFields :: (TagsObject a) => FieldList a b tagsFields = [ (FieldDefinition "tags" "Tags" QFTOther "Tags", - FieldSimple (rsNormal . tagsOf)) ] + FieldSimple (rsNormal . tagsOf), QffNormal) ] -- * Generic parameter functions @@ -169,4 +169,4 @@ buildNdParamField field = qft = fromMaybe QFTOther $ field `Map.lookup` ndParamTypes desc = "The \"" ++ field ++ "\" node parameter" in (FieldDefinition full_name title qft desc, - FieldConfig (ndParamGetter field)) + FieldConfig (ndParamGetter field), QffNormal) diff --git a/htools/Ganeti/Query/Filter.hs b/htools/Ganeti/Query/Filter.hs index 94f65c9a0..ad7faa236 100644 --- a/htools/Ganeti/Query/Filter.hs +++ b/htools/Ganeti/Query/Filter.hs @@ -69,12 +69,21 @@ import Ganeti.JSON -- | Compiles a filter based on field names to one based on getters. compileFilter :: FieldMap a b -> Filter FilterField - -> ErrorResult (Filter (FieldGetter a b)) + -> ErrorResult (Filter (FieldGetter a b, QffMode)) compileFilter fm = traverse (\field -> maybe (Bad . ParameterError $ "Can't find field named '" ++ field ++ "'") - (Ok . snd) (field `Map.lookup` fm)) + (\(_, g, q) -> Ok (g, q)) (field `Map.lookup` fm)) + +-- | Processes a field value given a QffMode. +qffField :: QffMode -> JSValue -> ErrorResult JSValue +qffField QffNormal v = Ok v +qffField QffTimestamp v = + case v of + JSArray [secs@(JSRational _ _), JSRational _ _] -> return secs + _ -> Bad $ ProgrammerError + "Internal error: Getter returned non-timestamp for QffTimestamp" -- | Wraps a getter, filter pair. If the getter is 'FieldRuntime' but -- we don't have a runtime context, we skip the filtering, returning @@ -82,15 +91,15 @@ compileFilter fm = wrapGetter :: ConfigData -> Maybe b -> a - -> FieldGetter a b + -> (FieldGetter a b, QffMode) -> (JSValue -> ErrorResult Bool) -> ErrorResult Bool -wrapGetter cfg b a getter faction = +wrapGetter cfg b a (getter, qff) faction = case tryGetter cfg b a getter of Nothing -> Ok True -- runtime missing, accepting the value Just v -> case v of - ResultEntry RSNormal (Just fval) -> faction fval + ResultEntry RSNormal (Just fval) -> qffField qff fval >>= faction ResultEntry RSNormal Nothing -> Bad $ ProgrammerError "Internal error: Getter returned RSNormal/Nothing" @@ -149,7 +158,7 @@ containsFilter (NumericValue val) lst = do -- 'any' and 'all' do not play nice with monadic values, resulting in -- either too much memory use or in too many thunks being created. evaluateFilter :: ConfigData -> Maybe b -> a - -> Filter (FieldGetter a b) + -> Filter (FieldGetter a b, QffMode) -> ErrorResult Bool evaluateFilter _ _ _ EmptyFilter = Ok True evaluateFilter c mb a (AndFilter flts) = helper flts diff --git a/htools/Ganeti/Query/Group.hs b/htools/Ganeti/Query/Group.hs index acf908381..7711deab0 100644 --- a/htools/Ganeti/Query/Group.hs +++ b/htools/Ganeti/Query/Group.hs @@ -43,39 +43,41 @@ groupFields :: FieldList NodeGroup GroupRuntime groupFields = [ (FieldDefinition "alloc_policy" "AllocPolicy" QFTText "Allocation policy for group", - FieldSimple (rsNormal . groupAllocPolicy)) + FieldSimple (rsNormal . groupAllocPolicy), QffNormal) , (FieldDefinition "custom_diskparams" "CustomDiskParameters" QFTOther "Custom disk parameters", - FieldSimple (rsNormal . groupDiskparams)) + FieldSimple (rsNormal . groupDiskparams), QffNormal) , (FieldDefinition "custom_ipolicy" "CustomInstancePolicy" QFTOther "Custom instance policy limitations", - FieldSimple (rsNormal . groupIpolicy)) + FieldSimple (rsNormal . groupIpolicy), QffNormal) , (FieldDefinition "custom_ndparams" "CustomNDParams" QFTOther "Custom node parameters", - FieldSimple (rsNormal . groupNdparams)) + FieldSimple (rsNormal . groupNdparams), QffNormal) , (FieldDefinition "diskparams" "DiskParameters" QFTOther "Disk parameters (merged)", - FieldConfig (\cfg -> rsNormal . getGroupDiskParams cfg)) + FieldConfig (\cfg -> rsNormal . getGroupDiskParams cfg), QffNormal) , (FieldDefinition "ipolicy" "InstancePolicy" QFTOther "Instance policy limitations (merged)", - FieldConfig (\cfg ng -> rsNormal (getGroupIpolicy cfg ng))) + FieldConfig (\cfg ng -> rsNormal (getGroupIpolicy cfg ng)), QffNormal) , (FieldDefinition "name" "Group" QFTText "Group name", - FieldSimple (rsNormal . groupName)) + FieldSimple (rsNormal . groupName), QffNormal) , (FieldDefinition "ndparams" "NDParams" QFTOther "Node parameters", - FieldConfig (\cfg ng -> rsNormal (getGroupNdParams cfg ng))) + FieldConfig (\cfg ng -> rsNormal (getGroupNdParams cfg ng)), QffNormal) , (FieldDefinition "node_cnt" "Nodes" QFTNumber "Number of nodes", - FieldConfig (\cfg -> rsNormal . length . getGroupNodes cfg . groupName)) + FieldConfig (\cfg -> rsNormal . length . getGroupNodes cfg . groupName), + QffNormal) , (FieldDefinition "node_list" "NodeList" QFTOther "List of nodes", FieldConfig (\cfg -> rsNormal . map nodeName . - getGroupNodes cfg . groupName)) + getGroupNodes cfg . groupName), QffNormal) , (FieldDefinition "pinst_cnt" "Instances" QFTNumber "Number of primary instances", FieldConfig - (\cfg -> rsNormal . length . fst . getGroupInstances cfg . groupName)) + (\cfg -> rsNormal . length . fst . getGroupInstances cfg . groupName), + QffNormal) , (FieldDefinition "pinst_list" "InstanceList" QFTOther "List of primary instances", FieldConfig (\cfg -> rsNormal . map instName . fst . - getGroupInstances cfg . groupName)) + getGroupInstances cfg . groupName), QffNormal) ] ++ map buildNdParamField allNDParamFields ++ timeStampFields ++ @@ -85,4 +87,5 @@ groupFields = -- | The group fields map. groupFieldsMap :: FieldMap NodeGroup GroupRuntime -groupFieldsMap = Map.fromList $ map (\v -> (fdefName (fst v), v)) groupFields +groupFieldsMap = + Map.fromList $ map (\v@(f, _, _) -> (fdefName f, v)) groupFields diff --git a/htools/Ganeti/Query/Node.hs b/htools/Ganeti/Query/Node.hs index 1fbc80eac..8809c316d 100644 --- a/htools/Ganeti/Query/Node.hs +++ b/htools/Ganeti/Query/Node.hs @@ -105,7 +105,8 @@ nodeLiveFieldBuilder :: (FieldName, FieldTitle, FieldType, String, FieldDoc) -> FieldData Node NodeRuntime nodeLiveFieldBuilder (fname, ftitle, ftype, _, fdoc) = ( FieldDefinition fname ftitle ftype fdoc - , FieldRuntime $ nodeLiveRpcCall fname) + , FieldRuntime $ nodeLiveRpcCall fname + , QffNormal) -- | The docstring for the node role. Note that we use 'reverse in -- order to keep the same order as Python. @@ -130,69 +131,73 @@ getNodePower cfg node = nodeFields :: FieldList Node NodeRuntime nodeFields = [ (FieldDefinition "drained" "Drained" QFTBool "Whether node is drained", - FieldSimple (rsNormal . nodeDrained)) + FieldSimple (rsNormal . nodeDrained), QffNormal) , (FieldDefinition "master_candidate" "MasterC" QFTBool "Whether node is a master candidate", - FieldSimple (rsNormal . nodeMasterCandidate)) + FieldSimple (rsNormal . nodeMasterCandidate), QffNormal) , (FieldDefinition "master_capable" "MasterCapable" QFTBool "Whether node can become a master candidate", - FieldSimple (rsNormal . nodeMasterCapable)) + FieldSimple (rsNormal . nodeMasterCapable), QffNormal) , (FieldDefinition "name" "Node" QFTText "Node name", - FieldSimple (rsNormal . nodeName)) + FieldSimple (rsNormal . nodeName), QffNormal) , (FieldDefinition "offline" "Offline" QFTBool "Whether node is marked offline", - FieldSimple (rsNormal . nodeOffline)) + FieldSimple (rsNormal . nodeOffline), QffNormal) , (FieldDefinition "vm_capable" "VMCapable" QFTBool "Whether node can host instances", - FieldSimple (rsNormal . nodeVmCapable)) + FieldSimple (rsNormal . nodeVmCapable), QffNormal) , (FieldDefinition "pip" "PrimaryIP" QFTText "Primary IP address", - FieldSimple (rsNormal . nodePrimaryIp)) + FieldSimple (rsNormal . nodePrimaryIp), QffNormal) , (FieldDefinition "sip" "SecondaryIP" QFTText "Secondary IP address", - FieldSimple (rsNormal . nodeSecondaryIp)) + FieldSimple (rsNormal . nodeSecondaryIp), QffNormal) , (FieldDefinition "master" "IsMaster" QFTBool "Whether node is master", FieldConfig (\cfg node -> rsNormal (nodeName node == - clusterMasterNode (configCluster cfg)))) + clusterMasterNode (configCluster cfg))), + QffNormal) , (FieldDefinition "group" "Group" QFTText "Node group", FieldConfig (\cfg node -> - rsMaybe (groupName <$> getGroupOfNode cfg node))) + rsMaybe (groupName <$> getGroupOfNode cfg node)), + QffNormal) , (FieldDefinition "group.uuid" "GroupUUID" QFTText "UUID of node group", - FieldSimple (rsNormal . nodeGroup)) + FieldSimple (rsNormal . nodeGroup), QffNormal) , (FieldDefinition "ndparams" "NodeParameters" QFTOther "Merged node parameters", - FieldConfig ((rsMaybe .) . getNodeNdParams)) + FieldConfig ((rsMaybe .) . getNodeNdParams), QffNormal) , (FieldDefinition "custom_ndparams" "CustomNodeParameters" QFTOther "Custom node parameters", - FieldSimple (rsNormal . nodeNdparams)) + FieldSimple (rsNormal . nodeNdparams), QffNormal) -- FIXME: the below could be generalised a bit, like in Python , (FieldDefinition "pinst_cnt" "Pinst" QFTNumber "Number of instances with this node as primary", FieldConfig (\cfg -> - rsNormal . length . fst . getNodeInstances cfg . nodeName)) + rsNormal . length . fst . getNodeInstances cfg . nodeName), + QffNormal) , (FieldDefinition "sinst_cnt" "Sinst" QFTNumber "Number of instances with this node as secondary", FieldConfig (\cfg -> - rsNormal . length . snd . getNodeInstances cfg . nodeName)) + rsNormal . length . snd . getNodeInstances cfg . nodeName), + QffNormal) , (FieldDefinition "pinst_list" "PriInstances" QFTOther "List of instances with this node as primary", FieldConfig (\cfg -> rsNormal . map instName . fst . - getNodeInstances cfg . nodeName)) + getNodeInstances cfg . nodeName), QffNormal) , (FieldDefinition "sinst_list" "SecInstances" QFTOther "List of instances with this node as secondary", FieldConfig (\cfg -> rsNormal . map instName . snd . - getNodeInstances cfg . nodeName)) + getNodeInstances cfg . nodeName), QffNormal) , (FieldDefinition "role" "Role" QFTText nodeRoleDoc, - FieldConfig ((rsNormal .) . getNodeRole)) + FieldConfig ((rsNormal .) . getNodeRole), QffNormal) , (FieldDefinition "powered" "Powered" QFTBool "Whether node is thought to be powered on", - FieldConfig getNodePower) + FieldConfig getNodePower, QffNormal) -- FIXME: the two fields below are incomplete in Python, part of the -- non-implemented node resource model; they are declared just for -- parity, but are not functional , (FieldDefinition "hv_state" "HypervisorState" QFTOther "Hypervisor state", - missingRuntime) + missingRuntime, QffNormal) , (FieldDefinition "disk_state" "DiskState" QFTOther "Disk state", - missingRuntime) + missingRuntime, QffNormal) ] ++ map nodeLiveFieldBuilder nodeLiveFieldsDefs ++ map buildNdParamField allNDParamFields ++ @@ -203,4 +208,5 @@ nodeFields = -- | The node fields map. nodeFieldsMap :: FieldMap Node NodeRuntime -nodeFieldsMap = Map.fromList $ map (\v -> (fdefName (fst v), v)) nodeFields +nodeFieldsMap = + Map.fromList $ map (\v@(f, _, _) -> (fdefName f, v)) nodeFields diff --git a/htools/Ganeti/Query/Query.hs b/htools/Ganeti/Query/Query.hs index 70fe0c06c..d99973227 100644 --- a/htools/Ganeti/Query/Query.hs +++ b/htools/Ganeti/Query/Query.hs @@ -79,7 +79,8 @@ import Ganeti.Utils mkUnknownFDef :: String -> FieldData a b mkUnknownFDef name = ( FieldDefinition name name QFTUnknown ("Unknown field '" ++ name ++ "'") - , FieldUnknown ) + , FieldUnknown + , QffNormal ) -- | Runs a field getter on the existing contexts. execGetter :: ConfigData -> b -> a -> FieldGetter a b -> ResultEntry @@ -161,7 +162,7 @@ queryInner cfg live (Query (ItemTypeOpCode QRNode) fields qfilter) wanted = runResultT $ do cfilter <- resultT $ compileFilter nodeFieldsMap qfilter let selected = getSelectedFields nodeFieldsMap fields - (fdefs, fgetters) = unzip selected + (fdefs, fgetters, _) = unzip3 selected live' = live && needsLiveData fgetters nodes <- resultT $ case wanted of [] -> Ok . niceSortKey nodeName . @@ -182,7 +183,7 @@ queryInner cfg _ (Query (ItemTypeOpCode QRGroup) fields qfilter) wanted = return $ do cfilter <- compileFilter groupFieldsMap qfilter let selected = getSelectedFields groupFieldsMap fields - (fdefs, fgetters) = unzip selected + (fdefs, fgetters, _) = unzip3 selected groups <- case wanted of [] -> Ok . niceSortKey groupName . Map.elems . fromContainer $ configNodegroups cfg @@ -202,7 +203,7 @@ fieldsExtractor fieldsMap fields = let selected = if null fields then map snd $ Map.toAscList fieldsMap else getSelectedFields fieldsMap fields - in QueryFieldsResult (map fst selected) + in QueryFieldsResult (map (\(defs, _, _) -> defs) selected) -- | Query fields call. queryFields :: QueryFields -> ErrorResult QueryFieldsResult diff --git a/htools/Ganeti/Query/Types.hs b/htools/Ganeti/Query/Types.hs index 42300b2cd..c9cbbbcdd 100644 --- a/htools/Ganeti/Query/Types.hs +++ b/htools/Ganeti/Query/Types.hs @@ -28,6 +28,7 @@ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA module Ganeti.Query.Types ( FieldGetter(..) + , QffMode(..) , FieldData , FieldList , FieldMap @@ -50,8 +51,16 @@ data FieldGetter a b = FieldSimple (a -> ResultEntry) | FieldConfig (ConfigData -> a -> ResultEntry) | FieldUnknown +-- | Type defining how the value of a field is used in filtering. This +-- implements the equivalent to Python's QFF_ flags, except that we +-- don't use OR-able values. +data QffMode = QffNormal -- ^ Value is used as-is in filters + | QffTimestamp -- ^ Value is a timestamp tuple, convert to float + deriving (Show, Eq) + + -- | Alias for a field data (definition and getter). -type FieldData a b = (FieldDefinition, FieldGetter a b) +type FieldData a b = (FieldDefinition, FieldGetter a b, QffMode) -- | Alias for a field data list. type FieldList a b = [FieldData a b] -- GitLab