| {-| Implementation of the Ganeti Query2 instance queries. |
| |
| -} |
| |
| {- |
| |
| Copyright (C) 2013 Google Inc. |
| All rights reserved. |
| |
| Redistribution and use in source and binary forms, with or without |
| modification, are permitted provided that the following conditions are |
| met: |
| |
| 1. Redistributions of source code must retain the above copyright notice, |
| this list of conditions and the following disclaimer. |
| |
| 2. Redistributions in binary form must reproduce the above copyright |
| notice, this list of conditions and the following disclaimer in the |
| documentation and/or other materials provided with the distribution. |
| |
| THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS |
| IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED |
| TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR |
| CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF |
| LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING |
| NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS |
| SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| |
| -} |
| |
| module Ganeti.Query.Instance |
| ( Runtime |
| , fieldsMap |
| , collectLiveData |
| , getInstanceInfo |
| , instanceFields |
| , instanceAliases |
| ) where |
| |
| import Control.Applicative |
| import Control.Monad (liftM, (>=>)) |
| import qualified Data.ByteString.UTF8 as UTF8 |
| import Data.Either |
| import Data.List |
| import Data.Maybe |
| import Data.Monoid |
| import qualified Data.Map as Map |
| import Data.Ord (comparing) |
| import qualified Text.JSON as J |
| import Text.Printf |
| |
| import Ganeti.BasicTypes |
| import Ganeti.Common |
| import Ganeti.Config |
| import qualified Ganeti.Constants as C |
| import qualified Ganeti.ConstantUtils as C |
| import Ganeti.Errors |
| import Ganeti.JSON (MaybeForJSON(..), fromContainer) |
| import Ganeti.Objects |
| import Ganeti.Query.Common |
| import Ganeti.Query.Language |
| import Ganeti.Query.Types |
| import Ganeti.Rpc |
| import Ganeti.Storage.Utils |
| import Ganeti.Types |
| import Ganeti.Utils (formatOrdinal) |
| |
| -- | The LiveInfo consists of two entries whose presence is independent. |
| -- The 'InstanceInfo' is the live instance information, accompanied by a bool |
| -- signifying if it was found on its designated primary node or not. |
| -- The 'InstanceConsoleInfo' describes how to connect to an instance. |
| -- Any combination of these may or may not be present, depending on node and |
| -- instance availability. |
| type LiveInfo = (Maybe (InstanceInfo, Bool), Maybe InstanceConsoleInfo) |
| |
| -- | Runtime containing the 'LiveInfo'. See the genericQuery function in |
| -- the Query.hs file for an explanation of the terms used. |
| type Runtime = Either RpcError LiveInfo |
| |
| -- | The instance fields map. |
| fieldsMap :: FieldMap Instance Runtime |
| fieldsMap = fieldListToFieldMap aliasedFields |
| |
| -- | The instance aliases. |
| instanceAliases :: [(FieldName, FieldName)] |
| instanceAliases = |
| [ ("vcpus", "be/vcpus") |
| , ("be/memory", "be/maxmem") |
| , ("sda_size", "disk.size/0") |
| , ("sdb_size", "disk.size/1") |
| , ("ip", "nic.ip/0") |
| , ("mac", "nic.mac/0") |
| , ("bridge", "nic.bridge/0") |
| , ("nic_mode", "nic.mode/0") |
| , ("nic_link", "nic.link/0") |
| , ("nic_network", "nic.network/0") |
| ] |
| |
| -- | The aliased instance fields. |
| aliasedFields :: FieldList Instance Runtime |
| aliasedFields = aliasFields instanceAliases instanceFields |
| |
| -- | The instance fields. |
| instanceFields :: FieldList Instance Runtime |
| instanceFields = |
| -- Simple fields |
| [ (FieldDefinition "admin_state" "InstanceState" QFTText |
| "Desired state of instance", |
| FieldSimple (rsMaybeNoData . liftM adminStateToRaw . instAdminState), |
| QffNormal) |
| , (FieldDefinition "admin_state_source" "InstanceStateSource" QFTText |
| "Who last changed the desired state of the instance", |
| FieldSimple (rsMaybeNoData . liftM adminStateSourceToRaw |
| . instAdminStateSource), |
| QffNormal) |
| , (FieldDefinition "admin_up" "Autostart" QFTBool |
| "Desired state of instance", |
| FieldSimple (rsMaybeNoData . liftM (== AdminUp) . instAdminState), |
| QffNormal) |
| , (FieldDefinition "disks_active" "DisksActive" QFTBool |
| "Desired state of instance disks", |
| FieldSimple (rsMaybeNoData . instDisksActive), QffNormal) |
| , (FieldDefinition "name" "Instance" QFTText |
| "Instance name", |
| FieldSimple (rsMaybeNoData . instName), QffHostname) |
| , (FieldDefinition "hypervisor" "Hypervisor" QFTText |
| "Hypervisor name", |
| FieldSimple (rsMaybeNoData . instHypervisor), QffNormal) |
| , (FieldDefinition "network_port" "Network_port" QFTOther |
| "Instance network port if available (e.g. for VNC console)", |
| FieldSimple (rsMaybeUnavail . instNetworkPort), QffNormal) |
| , (FieldDefinition "os" "OS" QFTText |
| "Operating system", |
| FieldSimple (rsMaybeNoData . instOs), QffNormal) |
| , (FieldDefinition "pnode" "Primary_node" QFTText |
| "Primary node", |
| FieldConfig getPrimaryNodeName, QffHostname) |
| , (FieldDefinition "pnode.group" "PrimaryNodeGroup" QFTText |
| "Primary node's group", |
| FieldConfig getPrimaryNodeGroupName, QffNormal) |
| , (FieldDefinition "pnode.group.uuid" "PrimaryNodeGroupUUID" QFTText |
| "Primary node's group UUID", |
| FieldConfig getPrimaryNodeGroupUuid, QffNormal) |
| , (FieldDefinition "snodes" "Secondary_Nodes" QFTOther |
| "Secondary nodes; usually this will just be one node", |
| FieldConfig (getSecondaryNodeAttribute nodeName), QffNormal) |
| , (FieldDefinition "snodes.group" "SecondaryNodesGroups" QFTOther |
| "Node groups of secondary nodes", |
| FieldConfig (getSecondaryNodeGroupAttribute groupName), QffNormal) |
| , (FieldDefinition "snodes.group.uuid" "SecondaryNodesGroupsUUID" QFTOther |
| "Node group UUIDs of secondary nodes", |
| FieldConfig (getSecondaryNodeGroupAttribute uuidOf), QffNormal) |
| ] ++ |
| |
| -- Instance parameter fields, whole |
| [ (FieldDefinition "hvparams" "HypervisorParameters" QFTOther |
| "Hypervisor parameters (merged)", |
| FieldConfig |
| ((rsNormal .) . getFilledInstHvParams (C.toList C.hvcGlobals)), |
| QffNormal), |
| |
| (FieldDefinition "beparams" "BackendParameters" QFTOther |
| "Backend parameters (merged)", |
| FieldConfig ((rsErrorNoData .) . getFilledInstBeParams), QffNormal) |
| , (FieldDefinition "osparams" "OpSysParameters" QFTOther |
| "Operating system parameters (merged)", |
| FieldConfig ((rsNormal .) . getFilledInstOsParams), QffNormal) |
| , (FieldDefinition "custom_hvparams" "CustomHypervisorParameters" QFTOther |
| "Custom hypervisor parameters", |
| FieldSimple (rsNormal . instHvparams), QffNormal) |
| , (FieldDefinition "custom_beparams" "CustomBackendParameters" QFTOther |
| "Custom backend parameters", |
| FieldSimple (rsNormal . instBeparams), QffNormal) |
| , (FieldDefinition "custom_osparams" "CustomOpSysParameters" QFTOther |
| "Custom operating system parameters", |
| FieldSimple (rsNormal . instOsparams), QffNormal) |
| , (FieldDefinition "custom_nicparams" "CustomNicParameters" QFTOther |
| "Custom network interface parameters", |
| FieldSimple (rsNormal . map nicNicparams . instNics), QffNormal) |
| ] ++ |
| |
| -- Instance parameter fields, generated |
| map (buildBeParamField beParamGetter) allBeParamFields ++ |
| map (buildHvParamField hvParamGetter) |
| (C.toList C.hvsParameters \\ C.toList C.hvcGlobals) ++ |
| |
| -- disk parameter fields |
| [ (FieldDefinition "disk_usage" "DiskUsage" QFTUnit |
| "Total disk space used by instance on each of its nodes; this is not the\ |
| \ disk size visible to the instance, but the usage on the node", |
| FieldConfig getDiskSizeRequirements, QffNormal) |
| , (FieldDefinition "disk.count" "Disks" QFTNumber |
| "Number of disks", |
| FieldSimple (rsNormal . length . instDisks), QffNormal) |
| , (FieldDefinition "disk.sizes" "Disk_sizes" QFTOther |
| "List of disk sizes", FieldConfig getDiskSizes, QffNormal) |
| , (FieldDefinition "disk.spindles" "Disk_spindles" QFTOther |
| "List of disk spindles", |
| FieldConfig getDiskSpindles, QffNormal) |
| , (FieldDefinition "disk.names" "Disk_names" QFTOther |
| "List of disk names", |
| FieldConfig getDiskNames, QffNormal) |
| , (FieldDefinition "disk.uuids" "Disk_UUIDs" QFTOther |
| "List of disk UUIDs", |
| FieldConfig getDiskUuids, QffNormal) |
| -- For pre-2.14 backwards compatibility |
| , (FieldDefinition "disk_template" "Disk_template" QFTText |
| "Instance disk template", |
| FieldConfig getDiskTemplate, QffNormal) |
| ] ++ |
| |
| -- Per-disk parameter fields |
| instantiateIndexedFields C.maxDisks |
| [ (fieldDefinitionCompleter "disk.size/%d" "Disk/%d" QFTUnit |
| "Disk size of %s disk", |
| getIndexedOptionalConfField getInstDisksFromObj diskSize, |
| QffNormal) |
| , (fieldDefinitionCompleter "disk.spindles/%d" "DiskSpindles/%d" QFTNumber |
| "Spindles of %s disk", |
| getIndexedOptionalConfField getInstDisksFromObj diskSpindles, |
| QffNormal) |
| , (fieldDefinitionCompleter "disk.name/%d" "DiskName/%d" QFTText |
| "Name of %s disk", |
| getIndexedOptionalConfField getInstDisksFromObj diskName, QffNormal) |
| , (fieldDefinitionCompleter "disk.uuid/%d" "DiskUUID/%d" QFTText |
| "UUID of %s disk", |
| getIndexedConfField getInstDisksFromObj uuidOf, QffNormal) |
| ] ++ |
| |
| -- Aggregate nic parameter fields |
| [ (FieldDefinition "nic.count" "NICs" QFTNumber |
| "Number of network interfaces", |
| FieldSimple (rsNormal . length . instNics), QffNormal) |
| , (FieldDefinition "nic.macs" "NIC_MACs" QFTOther |
| (nicAggDescPrefix ++ "MAC address"), |
| FieldSimple (rsNormal . map nicMac . instNics), QffNormal) |
| , (FieldDefinition "nic.ips" "NIC_IPs" QFTOther |
| (nicAggDescPrefix ++ "IP address"), |
| FieldSimple (rsNormal . map (MaybeForJSON . nicIp) . instNics), |
| QffNormal) |
| , (FieldDefinition "nic.names" "NIC_Names" QFTOther |
| (nicAggDescPrefix ++ "name"), |
| FieldSimple (rsNormal . map (MaybeForJSON . nicName) . instNics), |
| QffNormal) |
| , (FieldDefinition "nic.uuids" "NIC_UUIDs" QFTOther |
| (nicAggDescPrefix ++ "UUID"), |
| FieldSimple (rsNormal . map uuidOf . instNics), QffNormal) |
| , (FieldDefinition "nic.modes" "NIC_modes" QFTOther |
| (nicAggDescPrefix ++ "mode"), |
| FieldConfig (\cfg -> rsNormal . map |
| (nicpMode . fillNicParamsFromConfig cfg . nicNicparams) |
| . instNics), |
| QffNormal) |
| , (FieldDefinition "nic.vlans" "NIC_VLANs" QFTOther |
| (nicAggDescPrefix ++ "VLAN"), |
| FieldConfig (\cfg -> rsNormal . map (MaybeForJSON . getNicVlan . |
| fillNicParamsFromConfig cfg . nicNicparams) . instNics), |
| QffNormal) |
| , (FieldDefinition "nic.bridges" "NIC_bridges" QFTOther |
| (nicAggDescPrefix ++ "bridge"), |
| FieldConfig (\cfg -> rsNormal . map (MaybeForJSON . getNicBridge . |
| fillNicParamsFromConfig cfg . nicNicparams) . instNics), |
| QffNormal) |
| , (FieldDefinition "nic.links" "NIC_links" QFTOther |
| (nicAggDescPrefix ++ "link"), |
| FieldConfig (\cfg -> rsNormal . map |
| (nicpLink . fillNicParamsFromConfig cfg . nicNicparams) |
| . instNics), QffNormal) |
| , (FieldDefinition "nic.networks" "NIC_networks" QFTOther |
| "List containing each interface's network", |
| FieldSimple (rsNormal . map (MaybeForJSON . nicNetwork) . instNics), |
| QffNormal) |
| , (FieldDefinition "nic.networks.names" "NIC_networks_names" QFTOther |
| "List containing the name of each interface's network", |
| FieldConfig (\cfg -> rsNormal . map |
| (\x -> MaybeForJSON (getNetworkName cfg <$> nicNetwork x)) |
| . instNics), |
| QffNormal) |
| ] ++ |
| |
| -- Per-nic parameter fields |
| instantiateIndexedFields C.maxNics |
| [ (fieldDefinitionCompleter "nic.ip/%d" "NicIP/%d" QFTText |
| ("IP address" ++ nicDescSuffix), |
| getIndexedOptionalField instNics nicIp, QffNormal) |
| , (fieldDefinitionCompleter "nic.uuid/%d" "NicUUID/%d" QFTText |
| ("UUID address" ++ nicDescSuffix), |
| getIndexedField instNics uuidOf, QffNormal) |
| , (fieldDefinitionCompleter "nic.mac/%d" "NicMAC/%d" QFTText |
| ("MAC address" ++ nicDescSuffix), |
| getIndexedField instNics nicMac, QffNormal) |
| , (fieldDefinitionCompleter "nic.name/%d" "NicName/%d" QFTText |
| ("Name address" ++ nicDescSuffix), |
| getIndexedOptionalField instNics nicName, QffNormal) |
| , (fieldDefinitionCompleter "nic.network/%d" "NicNetwork/%d" QFTText |
| ("Network" ++ nicDescSuffix), |
| getIndexedOptionalField instNics nicNetwork, QffNormal) |
| , (fieldDefinitionCompleter "nic.mode/%d" "NicMode/%d" QFTText |
| ("Mode" ++ nicDescSuffix), |
| getIndexedNicField nicpMode, QffNormal) |
| , (fieldDefinitionCompleter "nic.link/%d" "NicLink/%d" QFTText |
| ("Link" ++ nicDescSuffix), |
| getIndexedNicField nicpLink, QffNormal) |
| , (fieldDefinitionCompleter "nic.vlan/%d" "NicVLAN/%d" QFTText |
| ("VLAN" ++ nicDescSuffix), |
| getOptionalIndexedNicField getNicVlan, QffNormal) |
| , (fieldDefinitionCompleter "nic.network.name/%d" "NicNetworkName/%d" QFTText |
| ("Network name" ++ nicDescSuffix), |
| getIndexedNicNetworkNameField, QffNormal) |
| , (fieldDefinitionCompleter "nic.bridge/%d" "NicBridge/%d" QFTText |
| ("Bridge" ++ nicDescSuffix), |
| getOptionalIndexedNicField getNicBridge, QffNormal) |
| ] ++ |
| |
| -- Live fields using special getters |
| [ (FieldDefinition "status" "Status" QFTText |
| statusDocText, |
| FieldConfigRuntime statusExtract, QffNormal) |
| , (FieldDefinition "oper_state" "Running" QFTBool |
| "Actual state of instance", |
| FieldRuntime operStatusExtract, QffNormal), |
| |
| (FieldDefinition "console" "Console" QFTOther |
| "Instance console information", |
| FieldRuntime consoleExtract, QffNormal) |
| ] ++ |
| |
| -- Simple live fields |
| map instanceLiveFieldBuilder instanceLiveFieldsDefs ++ |
| |
| -- Common fields |
| timeStampFields ++ |
| serialFields "Instance" ++ |
| uuidFields "Instance" ++ |
| forthcomingFields "Instance" ++ |
| tagsFields |
| |
| -- * Helper functions for node property retrieval |
| |
| -- | Constant suffix of network interface field descriptions. |
| nicDescSuffix ::String |
| nicDescSuffix = " of %s network interface" |
| |
| -- | Almost-constant suffix of aggregate network interface field descriptions. |
| nicAggDescPrefix ::String |
| nicAggDescPrefix = "List containing each network interface's " |
| |
| -- | Given a network name id, returns the network's name. |
| getNetworkName :: ConfigData -> String -> NonEmptyString |
| getNetworkName cfg = networkName . (Map.!) (fromContainer $ configNetworks cfg) |
| . UTF8.fromString |
| |
| -- | Gets the bridge of a NIC. |
| getNicBridge :: FilledNicParams -> Maybe String |
| getNicBridge nicParams |
| | nicpMode nicParams == NMBridged = Just $ nicpLink nicParams |
| | otherwise = Nothing |
| |
| -- | Gets the VLAN of a NIC. |
| getNicVlan :: FilledNicParams -> Maybe String |
| getNicVlan params |
| | nicpMode params == NMOvs = Just $ nicpVlan params |
| | otherwise = Nothing |
| |
| -- | Fill partial NIC params by using the defaults from the configuration. |
| fillNicParamsFromConfig :: ConfigData -> PartialNicParams -> FilledNicParams |
| fillNicParamsFromConfig cfg = fillParams (getDefaultNicParams cfg) |
| |
| -- | Retrieves the default network interface parameters. |
| getDefaultNicParams :: ConfigData -> FilledNicParams |
| getDefaultNicParams cfg = |
| (Map.!) (fromContainer . clusterNicparams . configCluster $ cfg) |
| $ UTF8.fromString C.ppDefault |
| |
| -- | Retrieves the real disk size requirements for all the disks of the |
| -- instance. This includes the metadata etc. and is different from the values |
| -- visible to the instance. |
| getDiskSizeRequirements :: ConfigData -> Instance -> ResultEntry |
| getDiskSizeRequirements cfg inst = |
| rsErrorNoData . liftA (sum . map getSize) . getInstDisksFromObj cfg $ inst |
| where |
| diskType x = lidDiskType <$> diskLogicalId x |
| getSize :: Disk -> Int |
| getSize disk = |
| let dt = diskType disk |
| in case dt of |
| Just DTDrbd8 -> fromMaybe 0 (diskSize disk) + C.drbdMetaSize |
| Just DTDiskless -> 0 |
| Just DTBlock -> 0 |
| _ -> fromMaybe 0 (diskSize disk) |
| |
| -- | Get a list of disk sizes for an instance |
| getDiskSizes :: ConfigData -> Instance -> ResultEntry |
| getDiskSizes cfg = |
| rsErrorNoData . liftA (map $ MaybeForJSON . diskSize) |
| . getInstDisksFromObj cfg |
| |
| -- | Get a list of disk spindles |
| getDiskSpindles :: ConfigData -> Instance -> ResultEntry |
| getDiskSpindles cfg = |
| rsErrorNoData . liftA (map (MaybeForJSON . diskSpindles)) . |
| getInstDisksFromObj cfg |
| |
| -- | Get a list of disk names for an instance |
| getDiskNames :: ConfigData -> Instance -> ResultEntry |
| getDiskNames cfg = |
| rsErrorNoData . liftA (map (MaybeForJSON . diskName)) . |
| getInstDisksFromObj cfg |
| |
| -- | Get a list of disk UUIDs for an instance |
| getDiskUuids :: ConfigData -> Instance -> ResultEntry |
| getDiskUuids cfg = |
| rsErrorNoData . liftA (map uuidOf) . getInstDisksFromObj cfg |
| |
| -- | Creates a functions which produces a FieldConfig 'FieldGetter' when fed |
| -- an index. Works for fields that may not return a value, expressed through |
| -- the Maybe monad. |
| getIndexedOptionalConfField :: (J.JSON b) |
| => (ConfigData -> Instance -> ErrorResult [a]) |
| -- ^ Extracts a list of objects |
| -> (a -> Maybe b) -- ^ Possibly gets a property |
| -- from an object |
| -> Int -- ^ Index in list to use |
| -> FieldGetter Instance Runtime -- ^ Result |
| getIndexedOptionalConfField extractor optPropertyGetter index = |
| let getProperty x = maybeAt index x >>= optPropertyGetter |
| in FieldConfig (\cfg -> |
| rsErrorMaybeUnavail . liftA getProperty . extractor cfg) |
| |
| -- | Creates a function which produces a FieldConfig 'FieldGetter' when fed |
| -- an index. Works only for fields that surely return a value. |
| getIndexedConfField :: (J.JSON b) |
| => (ConfigData -> Instance -> ErrorResult [a]) |
| -- ^ Extracts a list of objects |
| -> (a -> b) -- ^ Gets a property from an object |
| -> Int -- ^ Index in list to use |
| -> FieldGetter Instance Runtime -- ^ Result |
| getIndexedConfField extractor propertyGetter index = |
| let optPropertyGetter = Just . propertyGetter |
| in getIndexedOptionalConfField extractor optPropertyGetter index |
| |
| -- | Returns a field that retrieves a given NIC's network name. |
| getIndexedNicNetworkNameField :: Int -> FieldGetter Instance Runtime |
| getIndexedNicNetworkNameField index = |
| FieldConfig (\cfg inst -> rsMaybeUnavail $ do |
| nicObj <- maybeAt index $ instNics inst |
| nicNetworkId <- nicNetwork nicObj |
| return $ getNetworkName cfg nicNetworkId) |
| |
| -- | Gets a fillable NIC field. |
| getIndexedNicField :: (J.JSON a) |
| => (FilledNicParams -> a) |
| -> Int |
| -> FieldGetter Instance Runtime |
| getIndexedNicField getter = |
| getOptionalIndexedNicField (\x -> Just . getter $ x) |
| |
| -- | Gets an optional fillable NIC field. |
| getOptionalIndexedNicField :: (J.JSON a) |
| => (FilledNicParams -> Maybe a) |
| -> Int |
| -> FieldGetter Instance Runtime |
| getOptionalIndexedNicField = |
| getIndexedFieldWithDefault |
| (map nicNicparams . instNics) (\x _ -> getDefaultNicParams x) fillParams |
| |
| -- | Creates a function which produces a 'FieldGetter' when fed an index. Works |
| -- for fields that should be filled out through the use of a default. |
| getIndexedFieldWithDefault :: (J.JSON c) |
| => (Instance -> [a]) -- ^ Extracts a list of incomplete objects |
| -> (ConfigData -> Instance -> b) -- ^ Extracts the default object |
| -> (b -> a -> b) -- ^ Fills the default object |
| -> (b -> Maybe c) -- ^ Extracts an obj property |
| -> Int -- ^ Index in list to use |
| -> FieldGetter Instance Runtime -- ^ Result |
| getIndexedFieldWithDefault |
| listGetter defaultGetter fillFn propertyGetter index = |
| FieldConfig (\cfg inst -> rsMaybeUnavail $ do |
| incompleteObj <- maybeAt index $ listGetter inst |
| let defaultObj = defaultGetter cfg inst |
| completeObj = fillFn defaultObj incompleteObj |
| propertyGetter completeObj) |
| |
| -- | Creates a function which produces a 'FieldGetter' when fed an index. Works |
| -- for fields that may not return a value, expressed through the Maybe monad. |
| getIndexedOptionalField :: (J.JSON b) |
| => (Instance -> [a]) -- ^ Extracts a list of objects |
| -> (a -> Maybe b) -- ^ Possibly gets a property |
| -- from an object |
| -> Int -- ^ Index in list to use |
| -> FieldGetter Instance Runtime -- ^ Result |
| getIndexedOptionalField extractor optPropertyGetter index = |
| FieldSimple(\inst -> rsMaybeUnavail $ do |
| obj <- maybeAt index $ extractor inst |
| optPropertyGetter obj) |
| |
| -- | Creates a function which produces a 'FieldGetter' when fed an index. |
| -- Works only for fields that surely return a value. |
| getIndexedField :: (J.JSON b) |
| => (Instance -> [a]) -- ^ Extracts a list of objects |
| -> (a -> b) -- ^ Gets a property from an object |
| -> Int -- ^ Index in list to use |
| -> FieldGetter Instance Runtime -- ^ Result |
| getIndexedField extractor propertyGetter index = |
| let optPropertyGetter = Just . propertyGetter |
| in getIndexedOptionalField extractor optPropertyGetter index |
| |
| -- | Retrieves a value from an array at an index, using the Maybe monad to |
| -- indicate failure. |
| maybeAt :: Int -> [a] -> Maybe a |
| maybeAt index list |
| | index >= length list = Nothing |
| | otherwise = Just $ list !! index |
| |
| -- | Primed with format strings for everything but the type, it consumes two |
| -- values and uses them to complete the FieldDefinition. |
| -- Warning: a bit unsafe as it uses printf. Handle with care. |
| fieldDefinitionCompleter :: (PrintfArg t1) => (PrintfArg t2) |
| => FieldName |
| -> FieldTitle |
| -> FieldType |
| -> FieldDoc |
| -> t1 |
| -> t2 |
| -> FieldDefinition |
| fieldDefinitionCompleter fName fTitle fType fDoc firstVal secondVal = |
| FieldDefinition (printf fName firstVal) |
| (printf fTitle firstVal) |
| fType |
| (printf fDoc secondVal) |
| |
| -- | Given an incomplete field definition and values that can complete it, |
| -- return a fully functional FieldData. Cannot work for all cases, should be |
| -- extended as necessary. |
| fillIncompleteFields :: (t1 -> t2 -> FieldDefinition, |
| t1 -> FieldGetter a b, |
| QffMode) |
| -> t1 |
| -> t2 |
| -> FieldData a b |
| fillIncompleteFields (iDef, iGet, mode) firstVal secondVal = |
| (iDef firstVal secondVal, iGet firstVal, mode) |
| |
| -- | Given indexed fields that describe lists, complete / instantiate them for |
| -- a given list size. |
| instantiateIndexedFields :: (Show t1, Integral t1) |
| => Int -- ^ The size of the list |
| -> [(t1 -> String -> FieldDefinition, |
| t1 -> FieldGetter a b, |
| QffMode)] -- ^ The indexed fields |
| -> FieldList a b -- ^ A list of complete fields |
| instantiateIndexedFields listSize fields = do |
| index <- take listSize [0..] |
| field <- fields |
| return . fillIncompleteFields field index . formatOrdinal $ index + 1 |
| |
| -- * Various helper functions for property retrieval |
| |
| -- | Helper function for primary node retrieval |
| getPrimaryNode :: ConfigData -> Instance -> ErrorResult Node |
| getPrimaryNode cfg = maybe (Bad $ ParameterError "no primary node") return |
| . instName |
| >=> getInstPrimaryNode cfg |
| |
| -- | Get primary node hostname |
| getPrimaryNodeName :: ConfigData -> Instance -> ResultEntry |
| getPrimaryNodeName cfg inst = |
| rsErrorNoData $ nodeName <$> getPrimaryNode cfg inst |
| |
| -- | Get primary node group |
| getPrimaryNodeGroup :: ConfigData -> Instance -> ErrorResult NodeGroup |
| getPrimaryNodeGroup cfg inst = do |
| pNode <- getPrimaryNode cfg inst |
| maybeToError "Configuration missing" $ getGroupOfNode cfg pNode |
| |
| -- | Get primary node group name |
| getPrimaryNodeGroupName :: ConfigData -> Instance -> ResultEntry |
| getPrimaryNodeGroupName cfg inst = |
| rsErrorNoData $ groupName <$> getPrimaryNodeGroup cfg inst |
| |
| -- | Get primary node group uuid |
| getPrimaryNodeGroupUuid :: ConfigData -> Instance -> ResultEntry |
| getPrimaryNodeGroupUuid cfg inst = |
| rsErrorNoData $ uuidOf <$> getPrimaryNodeGroup cfg inst |
| |
| -- | Get secondary nodes - the configuration objects themselves |
| getSecondaryNodes :: ConfigData -> Instance -> ErrorResult [Node] |
| getSecondaryNodes cfg inst = do |
| pNode <- getPrimaryNode cfg inst |
| iname <- maybe (Bad $ ParameterError "no name") return $ instName inst |
| allNodes <- getInstAllNodes cfg iname |
| return $ delete pNode allNodes |
| |
| -- | Get attributes of the secondary nodes |
| getSecondaryNodeAttribute :: (J.JSON a) |
| => (Node -> a) |
| -> ConfigData |
| -> Instance |
| -> ResultEntry |
| getSecondaryNodeAttribute getter cfg inst = |
| rsErrorNoData $ map (J.showJSON . getter) <$> getSecondaryNodes cfg inst |
| |
| -- | Get secondary node groups |
| getSecondaryNodeGroups :: ConfigData -> Instance -> ErrorResult [NodeGroup] |
| getSecondaryNodeGroups cfg inst = do |
| sNodes <- getSecondaryNodes cfg inst |
| return . catMaybes $ map (getGroupOfNode cfg) sNodes |
| |
| -- | Get attributes of secondary node groups |
| getSecondaryNodeGroupAttribute :: (J.JSON a) |
| => (NodeGroup -> a) |
| -> ConfigData |
| -> Instance |
| -> ResultEntry |
| getSecondaryNodeGroupAttribute getter cfg inst = |
| rsErrorNoData $ map (J.showJSON . getter) <$> getSecondaryNodeGroups cfg inst |
| |
| -- | Beparam getter builder: given a field, it returns a FieldConfig |
| -- getter, that is a function that takes the config and the object and |
| -- returns the Beparam field specified when the getter was built. |
| beParamGetter :: String -- ^ The field we are building the getter for |
| -> ConfigData -- ^ The configuration object |
| -> Instance -- ^ The instance configuration object |
| -> ResultEntry -- ^ The result |
| beParamGetter field config inst = |
| case getFilledInstBeParams config inst of |
| Ok beParams -> dictFieldGetter field $ Just beParams |
| Bad _ -> rsNoData |
| |
| -- | Hvparam getter builder: given a field, it returns a FieldConfig |
| -- getter, that is a function that takes the config and the object and |
| -- returns the Hvparam field specified when the getter was built. |
| hvParamGetter :: String -- ^ The field we're building the getter for |
| -> ConfigData -> Instance -> ResultEntry |
| hvParamGetter field cfg inst = |
| rsMaybeUnavail . Map.lookup (UTF8.fromString field) . fromContainer $ |
| getFilledInstHvParams (C.toList C.hvcGlobals) cfg inst |
| |
| -- * Live fields functionality |
| |
| -- | List of node live fields. |
| instanceLiveFieldsDefs :: [(FieldName, FieldTitle, FieldType, String, FieldDoc)] |
| instanceLiveFieldsDefs = |
| [ ("oper_ram", "Memory", QFTUnit, "oper_ram", |
| "Actual memory usage as seen by hypervisor") |
| , ("oper_vcpus", "VCPUs", QFTNumber, "oper_vcpus", |
| "Actual number of VCPUs as seen by hypervisor") |
| ] |
| |
| -- | Map each name to a function that extracts that value from the RPC result. |
| instanceLiveFieldExtract :: FieldName -> InstanceInfo -> Instance -> J.JSValue |
| instanceLiveFieldExtract "oper_ram" info _ = J.showJSON $ instInfoMemory info |
| instanceLiveFieldExtract "oper_vcpus" info _ = J.showJSON $ instInfoVcpus info |
| instanceLiveFieldExtract n _ _ = J.showJSON $ |
| "The field " ++ n ++ " is not an expected or extractable live field!" |
| |
| -- | Helper for extracting an instance live field from the RPC results. |
| instanceLiveRpcCall :: FieldName -> Runtime -> Instance -> ResultEntry |
| instanceLiveRpcCall fname (Right (Just (res, _), _)) inst = |
| case instanceLiveFieldExtract fname res inst of |
| J.JSNull -> rsNoData |
| x -> rsNormal x |
| instanceLiveRpcCall _ (Right (Nothing, _)) _ = rsUnavail |
| instanceLiveRpcCall _ (Left err) _ = |
| ResultEntry (rpcErrorToStatus err) Nothing |
| |
| -- | Builder for node live fields. |
| instanceLiveFieldBuilder :: (FieldName, FieldTitle, FieldType, String, FieldDoc) |
| -> FieldData Instance Runtime |
| instanceLiveFieldBuilder (fname, ftitle, ftype, _, fdoc) = |
| ( FieldDefinition fname ftitle ftype fdoc |
| , FieldRuntime $ instanceLiveRpcCall fname |
| , QffNormal) |
| |
| -- * Functionality related to status and operational status extraction |
| |
| -- | The documentation text for the instance status field |
| statusDocText :: String |
| statusDocText = |
| let si = show . instanceStatusToRaw :: InstanceStatus -> String |
| in "Instance status; " ++ |
| si Running ++ |
| " if instance is set to be running and actually is, " ++ |
| si StatusDown ++ |
| " if instance is stopped and is not running, " ++ |
| si WrongNode ++ |
| " if instance running, but not on its designated primary node, " ++ |
| si ErrorUp ++ |
| " if instance should be stopped, but is actually running, " ++ |
| si ErrorDown ++ |
| " if instance should run, but doesn't, " ++ |
| si NodeDown ++ |
| " if instance's primary node is down, " ++ |
| si NodeOffline ++ |
| " if instance's primary node is marked offline, " ++ |
| si StatusOffline ++ |
| " if instance is offline and does not use dynamic resources" |
| |
| -- | Checks if the primary node of an instance is offline |
| isPrimaryOffline :: ConfigData -> Instance -> Bool |
| isPrimaryOffline cfg inst = |
| let pNodeResult = maybe (Bad $ ParameterError "no primary node") return |
| (instPrimaryNode inst) |
| >>= getNode cfg |
| in case pNodeResult of |
| Ok pNode -> nodeOffline pNode |
| Bad _ -> error "Programmer error - result assumed to be OK is Bad!" |
| |
| -- | Determines if user shutdown reporting is enabled |
| userShutdownEnabled :: ConfigData -> Bool |
| userShutdownEnabled = clusterEnabledUserShutdown . configCluster |
| |
| -- | Determines the status of a live instance |
| liveInstanceStatus :: ConfigData |
| -> (InstanceInfo, Bool) |
| -> Instance |
| -> InstanceStatus |
| liveInstanceStatus cfg (instInfo, foundOnPrimary) inst |
| | not foundOnPrimary = WrongNode |
| | otherwise = |
| case instanceState of |
| InstanceStateRunning |
| | adminState == Just AdminUp -> Running |
| | otherwise -> ErrorUp |
| InstanceStateShutdown |
| | adminState == Just AdminUp && allowDown -> UserDown |
| | adminState == Just AdminUp -> ErrorDown |
| | otherwise -> StatusDown |
| where adminState = instAdminState inst |
| instanceState = instInfoState instInfo |
| |
| hvparams = |
| fromContainer $ getFilledInstHvParams (C.toList C.hvcGlobals) cfg inst |
| |
| allowDown = |
| userShutdownEnabled cfg && |
| (instHypervisor inst /= Just Kvm || |
| (Map.member (UTF8.fromString C.hvKvmUserShutdown) hvparams && |
| hvparams Map.! UTF8.fromString C.hvKvmUserShutdown |
| == J.JSBool True)) |
| |
| -- | Determines the status of a dead instance. |
| deadInstanceStatus :: ConfigData -> Instance -> InstanceStatus |
| deadInstanceStatus cfg inst = |
| case instAdminState inst of |
| Just AdminUp -> ErrorDown |
| Just AdminDown | wasCleanedUp && userShutdownEnabled cfg -> UserDown |
| | otherwise -> StatusDown |
| Just AdminOffline -> StatusOffline |
| Nothing -> StatusDown |
| where wasCleanedUp = instAdminStateSource inst == Just UserSource |
| |
| -- | Determines the status of the instance, depending on whether it is possible |
| -- to communicate with its primary node, on which node it is, and its |
| -- configuration. |
| determineInstanceStatus :: ConfigData -- ^ The configuration data |
| -> Runtime -- ^ All the data from the live call |
| -> Instance -- ^ Static instance configuration |
| -> InstanceStatus -- ^ Result |
| determineInstanceStatus cfg res inst |
| | isPrimaryOffline cfg inst = NodeOffline |
| | otherwise = case res of |
| Left _ -> NodeDown |
| Right (Just liveData, _) -> liveInstanceStatus cfg liveData inst |
| Right (Nothing, _) -> deadInstanceStatus cfg inst |
| |
| -- | Extracts the instance status, retrieving it using the functions above and |
| -- transforming it into a 'ResultEntry'. |
| statusExtract :: ConfigData -> Runtime -> Instance -> ResultEntry |
| statusExtract cfg res inst = |
| rsNormal . J.showJSON . instanceStatusToRaw $ |
| determineInstanceStatus cfg res inst |
| |
| -- | Extracts the operational status of the instance. |
| operStatusExtract :: Runtime -> Instance -> ResultEntry |
| operStatusExtract res _ = |
| rsMaybeNoData $ J.showJSON <$> |
| case res of |
| Left _ -> Nothing |
| Right (x, _) -> Just $ isJust x |
| |
| -- | Extracts the console connection information |
| consoleExtract :: Runtime -> Instance -> ResultEntry |
| consoleExtract (Left err) _ = ResultEntry (rpcErrorToStatus err) Nothing |
| consoleExtract (Right (_, val)) _ = rsMaybeNoData val |
| |
| -- * Helper functions extracting information as necessary for the generic query |
| -- interfaces |
| |
| -- | This function checks if a node with a given uuid has experienced an error |
| -- or not. |
| checkForNodeError :: [(String, ERpcError a)] |
| -> String |
| -> Maybe RpcError |
| checkForNodeError uuidList uuid = |
| case snd <$> pickPairUnique uuid uuidList of |
| Just (Left err) -> Just err |
| Just (Right _) -> Nothing |
| Nothing -> Just . RpcResultError $ |
| "Node response not present" |
| |
| -- | Finds information about the instance in the info delivered by a node |
| findInfoInNodeResult :: Instance |
| -> ERpcError RpcResultAllInstancesInfo |
| -> Maybe InstanceInfo |
| findInfoInNodeResult inst nodeResponse = |
| case nodeResponse of |
| Left _err -> Nothing |
| Right allInfo -> |
| let instances = rpcResAllInstInfoInstances allInfo |
| maybeMatch = instName inst >>= (`pickPairUnique` instances) |
| in snd <$> maybeMatch |
| |
| -- | Retrieves the instance information if it is present anywhere in the all |
| -- instances RPC result. Notes if it originates from the primary node. |
| -- An error is delivered if there is no result, and the primary node is down. |
| getInstanceInfo :: [(String, ERpcError RpcResultAllInstancesInfo)] |
| -> Instance |
| -> ERpcError (Maybe (InstanceInfo, Bool)) |
| getInstanceInfo uuidList inst = |
| case instPrimaryNode inst of |
| Nothing -> Right Nothing |
| Just pNodeUuid -> |
| let primarySearchResult = |
| pickPairUnique pNodeUuid uuidList >>= findInfoInNodeResult inst |
| . snd |
| in case primarySearchResult of |
| Just instInfo -> Right . Just $ (instInfo, True) |
| Nothing -> |
| let allSearchResult = |
| getFirst . mconcat $ map |
| (First . findInfoInNodeResult inst . snd) uuidList |
| in case allSearchResult of |
| Just instInfo -> Right . Just $ (instInfo, False) |
| Nothing -> |
| case checkForNodeError uuidList pNodeUuid of |
| Just err -> Left err |
| Nothing -> Right Nothing |
| |
| -- | Retrieves the console information if present anywhere in the given results |
| getConsoleInfo :: [(String, ERpcError RpcResultInstanceConsoleInfo)] |
| -> Instance |
| -> Maybe InstanceConsoleInfo |
| getConsoleInfo uuidList inst = |
| let allValidResults = concatMap rpcResInstConsInfoInstancesInfo . |
| rights . map snd $ uuidList |
| in snd <$> (instName inst >>= flip pickPairUnique allValidResults) |
| |
| -- | Extracts all the live information that can be extracted. |
| extractLiveInfo :: [(Node, ERpcError RpcResultAllInstancesInfo)] |
| -> [(Node, ERpcError RpcResultInstanceConsoleInfo)] |
| -> Instance |
| -> Runtime |
| extractLiveInfo nodeResultList nodeConsoleList inst = |
| let uuidConvert = map (\(x, y) -> (uuidOf x, y)) |
| uuidResultList = uuidConvert nodeResultList |
| uuidConsoleList = uuidConvert nodeConsoleList |
| in case getInstanceInfo uuidResultList inst of |
| -- If we can't get the instance info, we can't get the console info either. |
| -- Best to propagate the error further. |
| Left err -> Left err |
| Right res -> Right (res, getConsoleInfo uuidConsoleList inst) |
| |
| -- | Retrieves all the parameters for the console calls. |
| getAllConsoleParams :: ConfigData |
| -> [Instance] |
| -> ErrorResult [InstanceConsoleInfoParams] |
| getAllConsoleParams cfg = mapM $ \i -> |
| InstanceConsoleInfoParams i |
| <$> getPrimaryNode cfg i |
| <*> getPrimaryNodeGroup cfg i |
| <*> pure (getFilledInstHvParams [] cfg i) |
| <*> getFilledInstBeParams cfg i |
| |
| -- | Compares two params according to their node, needed for grouping. |
| compareParamsByNode :: InstanceConsoleInfoParams |
| -> InstanceConsoleInfoParams |
| -> Bool |
| compareParamsByNode x y = instConsInfoParamsNode x == instConsInfoParamsNode y |
| |
| -- | Groups instance information calls heading out to the same nodes. |
| consoleParamsToCalls :: [InstanceConsoleInfoParams] |
| -> [(Node, RpcCallInstanceConsoleInfo)] |
| consoleParamsToCalls params = |
| let sortedParams = sortBy |
| (comparing (instPrimaryNode . instConsInfoParamsInstance)) params |
| groupedParams = groupBy compareParamsByNode sortedParams |
| in map (\x -> case x of |
| [] -> error "Programmer error: group must have one or more members" |
| paramGroup@(y:_) -> |
| let node = instConsInfoParamsNode y |
| packer z = do |
| name <- instName $ instConsInfoParamsInstance z |
| return (name, z) |
| in (node, RpcCallInstanceConsoleInfo . mapMaybe packer |
| $ paramGroup) |
| ) groupedParams |
| |
| -- | Retrieves a list of all the hypervisors and params used by the given |
| -- instances. |
| getHypervisorSpecs :: ConfigData -> [Instance] -> [(Hypervisor, HvParams)] |
| getHypervisorSpecs cfg instances = |
| let hvs = nub . mapMaybe instHypervisor $ instances |
| hvParamMap = (fromContainer . clusterHvparams . configCluster $ cfg) |
| in zip hvs . map ((Map.!) hvParamMap) $ hvs |
| |
| -- | Collect live data from RPC query if enabled. |
| collectLiveData :: Bool -- ^ Live queries allowed |
| -> ConfigData -- ^ The cluster config |
| -> [String] -- ^ The requested fields |
| -> [Instance] -- ^ The instance objects |
| -> IO [(Instance, Runtime)] |
| collectLiveData liveDataEnabled cfg fields instances |
| | not liveDataEnabled = return . zip instances . repeat . Left . |
| RpcResultError $ "Live data disabled" |
| | otherwise = do |
| let hvSpecs = getHypervisorSpecs cfg instances |
| instanceNodes = |
| nub . justOk |
| $ map ( maybe (Bad $ ParameterError "no primary node") return |
| . instPrimaryNode |
| >=> getNode cfg) instances |
| goodNodes = nodesWithValidConfig cfg instanceNodes |
| instInfoRes <- executeRpcCall goodNodes (RpcCallAllInstancesInfo hvSpecs) |
| consInfoRes <- |
| if "console" `elem` fields |
| then case getAllConsoleParams cfg instances of |
| Ok p -> executeRpcCalls $ consoleParamsToCalls p |
| Bad _ -> return . zip goodNodes . repeat . Left $ |
| RpcResultError "Cannot construct parameters for console info call" |
| else return [] -- The information is not necessary |
| return . zip instances . |
| map (extractLiveInfo instInfoRes consInfoRes) $ instances |
| |
| -- | An aggregate disk attribute for backward compatibility. |
| getDiskTemplate :: ConfigData -> Instance -> ResultEntry |
| getDiskTemplate cfg inst = |
| let disks = getInstDisksFromObj cfg inst |
| getDt x = lidDiskType <$> diskLogicalId x |
| disk_types :: ErrorResult [DiskTemplate] |
| disk_types = nub <$> catMaybes <$> map getDt <$> disks |
| mix :: [DiskTemplate] -> J.JSValue |
| mix [] = J.showJSON C.dtDiskless |
| mix [t] = J.showJSON t |
| mix _ = J.showJSON C.dtMixed |
| in case mix <$> disk_types of |
| Ok t -> rsNormal t |
| Bad _ -> rsNoData |