diff --git a/Makefile.am b/Makefile.am index b96b42a110d3a5e0adae4fb06f6344919d6fb4fd..bd0e4dcedd3a35fa59bacb44fdf9bf75b9f24580 100644 --- a/Makefile.am +++ b/Makefile.am @@ -413,6 +413,7 @@ HS_LIB_SRCS = \ htools/Ganeti/Objects.hs \ htools/Ganeti/OpCodes.hs \ htools/Ganeti/Rpc.hs \ + htools/Ganeti/Queryd.hs \ htools/Ganeti/Runtime.hs \ htools/Ganeti/Ssconf.hs \ htools/Ganeti/THH.hs \ diff --git a/htools/Ganeti/Queryd.hs b/htools/Ganeti/Queryd.hs new file mode 100644 index 0000000000000000000000000000000000000000..4f265bdfb5e5c63877782ee6b2e1c13c1f54f259 --- /dev/null +++ b/htools/Ganeti/Queryd.hs @@ -0,0 +1,183 @@ +{-# LANGUAGE BangPatterns #-} + +{-| Implementation of the Ganeti confd types. + +-} + +{- + +Copyright (C) 2012 Google Inc. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA +02110-1301, USA. + +-} + +module Ganeti.Queryd + +where + +import Control.Concurrent +import Control.Exception +import Data.Bits (bitSize) +import Data.Maybe +import qualified Network.Socket as S +import qualified Text.JSON as J +import Text.JSON (showJSON, JSValue(..)) +import Text.JSON.Pretty (pp_value) +import System.Info (arch) + +import qualified Ganeti.Constants as C +import Ganeti.Objects +--import Ganeti.Config +import Ganeti.BasicTypes +import Ganeti.Logging +import Ganeti.Luxi + + +-- | A type for functions that can return the configuration when +-- executed. +type ConfigReader = IO (Result ConfigData) + +-- | Minimal wrapper to handle the missing config case. +handleCallWrapper :: Result ConfigData -> LuxiOp -> IO (Result JSValue) +handleCallWrapper (Bad msg) _ = + return . Bad $ "I do not have access to a valid configuration, cannot\ + \ process queries: " ++ msg +handleCallWrapper (Ok config) op = handleCall config op + +-- | Actual luxi operation handler. +handleCall :: ConfigData -> LuxiOp -> IO (Result JSValue) +handleCall cdata QueryClusterInfo = + let cluster = configCluster cdata + hypervisors = clusterEnabledHypervisors cluster + bits = show (bitSize (0::Int)) ++ "bits" + arch_tuple = [bits, arch] + -- FIXME: this is for the missing *params fields + empty_params = showJSON $ J.makeObj ([]::[(String, JSValue)]) + obj = [ ("software_version", showJSON $ C.releaseVersion) + , ("protocol_version", showJSON $ C.protocolVersion) + , ("config_version", showJSON $ C.configVersion) + , ("os_api_version", showJSON $ maximum C.osApiVersions) + , ("export_version", showJSON $ C.exportVersion) + , ("architecture", showJSON $ arch_tuple) + , ("name", showJSON $ clusterClusterName cluster) + , ("master", showJSON $ clusterMasterNode cluster) + , ("default_hypervisor", showJSON $ head hypervisors) + , ("enabled_hypervisors", showJSON $ hypervisors) + -- FIXME: *params missing + , ("hvparams", empty_params) + , ("os_hvp", empty_params) + , ("beparams", showJSON $ clusterBeparams cluster) + , ("osparams", showJSON $ clusterOsparams cluster) + , ("ipolicy", showJSON $ clusterIpolicy cluster) + , ("nicparams", showJSON $ clusterNicparams cluster) + , ("ndparams", showJSON $ clusterNdparams cluster) + , ("diskparams", empty_params) + , ("candidate_pool_size", + showJSON $ clusterCandidatePoolSize cluster) + , ("master_netdev", showJSON $ clusterMasterNetdev cluster) + , ("master_netmask", showJSON $ clusterMasterNetmask cluster) + , ("use_external_mip_script", + showJSON $ clusterUseExternalMipScript cluster) + , ("volume_group_name", showJSON $clusterVolumeGroupName cluster) + , ("drbd_usermode_helper", + maybe JSNull showJSON (clusterDrbdUsermodeHelper cluster)) + , ("file_storage_dir", showJSON $ clusterFileStorageDir cluster) + , ("shared_file_storage_dir", + showJSON $ clusterSharedFileStorageDir cluster) + , ("maintain_node_health", + showJSON $ clusterMaintainNodeHealth cluster) + , ("ctime", showJSON $ clusterCtime cluster) + , ("mtime", showJSON $ clusterMtime cluster) + , ("uuid", showJSON $ clusterUuid cluster) + , ("tags", showJSON $ clusterTags cluster) + , ("uid_pool", showJSON $ clusterUidPool cluster) + , ("default_iallocator", + showJSON $ clusterDefaultIallocator cluster) + , ("reserved_lvs", showJSON $ clusterReservedLvs cluster) + , ("primary_ip_version", + showJSON . ipFamilyToVersion $ clusterPrimaryIpFamily cluster) + , ("prealloc_wipe_disks", + showJSON $ clusterPreallocWipeDisks cluster) + , ("hidden_os", showJSON $ clusterHiddenOs cluster) + , ("blacklisted_os", showJSON $ clusterBlacklistedOs cluster) + ] + + in return . Ok . J.makeObj $ obj + +handleCall _ op = + return . Bad $ "Luxi call '" ++ strOfOp op ++ "' not implemented" + + +-- | Given a decoded luxi request, executes it and sends the luxi +-- response back to the client. +handleClientMsg :: Client -> ConfigReader -> LuxiOp -> IO Bool +handleClientMsg client creader args = do + cfg <- creader + logDebug $ "Request: " ++ show args + call_result <- handleCallWrapper cfg args + (!status, !rval) <- + case call_result of + Bad x -> do + logWarning $ "Failed to execute request: " ++ x + return (False, JSString $ J.toJSString x) + Ok result -> do + logDebug $ "Result " ++ show (pp_value result) + return (True, result) + sendMsg client $ buildResponse status rval + return True + +-- | Handles one iteration of the client protocol: receives message, +-- checks for validity and decods, returns response. +handleClient :: Client -> ConfigReader -> IO Bool +handleClient client creader = do + !msg <- recvMsgExt client + case msg of + RecvConnClosed -> logDebug "Connection closed" >> return False + RecvError err -> logWarning ("Error during message receiving: " ++ err) >> + return False + RecvOk payload -> + case validateCall payload >>= decodeCall of + Bad err -> logWarning ("Failed to parse request: " ++ err) >> + return False + Ok args -> handleClientMsg client creader args + +-- | Main client loop: runs one loop of 'handleClient', and if that +-- doesn't repot a finished (closed) connection, restarts itself. +clientLoop :: Client -> ConfigReader -> IO () +clientLoop client creader = do + result <- handleClient client creader + if result + then clientLoop client creader + else closeClient client + +-- | Main loop: accepts clients, forks an I/O thread to handle that +-- client, and then restarts. +mainLoop :: ConfigReader -> S.Socket -> IO () +mainLoop creader socket = do + client <- acceptClient socket + _ <- forkIO $ clientLoop client creader + mainLoop creader socket + +-- | Main function that runs the query endpoint. This should be the +-- only one exposed from this module. +runQueryD :: Maybe FilePath -> ConfigReader -> IO () +runQueryD fpath creader = do + let socket_path = fromMaybe C.querySocket fpath + bracket + (getServer socket_path) + (closeServer socket_path) + (mainLoop creader)