1 {-# LANGUAGE BangPatterns #-}
3 {-| Implementation of the Ganeti Query2 server.
9 Copyright (C) 2012, 2013 Google Inc.
11 This program is free software; you can redistribute it and/or modify
12 it under the terms of the GNU General Public License as published by
13 the Free Software Foundation; either version 2 of the License, or
14 (at your option) any later version.
16 This program is distributed in the hope that it will be useful, but
17 WITHOUT ANY WARRANTY; without even the implied warranty of
18 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 General Public License for more details.
21 You should have received a copy of the GNU General Public License
22 along with this program; if not, write to the Free Software
23 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
28 module Ganeti.Query.Server
34 import Control.Applicative
35 import Control.Concurrent
36 import Control.Exception
37 import Control.Monad (forever)
38 import Data.Bits (bitSize)
40 import qualified Network.Socket as S
41 import qualified Text.JSON as J
42 import Text.JSON (showJSON, JSValue(..))
43 import System.Info (arch)
45 import qualified Ganeti.Constants as C
47 import qualified Ganeti.Path as Path
50 import qualified Ganeti.Config as Config
51 import Ganeti.ConfigReader
52 import Ganeti.BasicTypes
55 import Ganeti.OpCodes (TagObject(..))
56 import qualified Ganeti.Query.Language as Qlang
57 import qualified Ganeti.Query.Cluster as QCluster
58 import Ganeti.Query.Query
59 import Ganeti.Query.Filter (makeSimpleFilter)
61 -- | Helper for classic queries.
62 handleClassicQuery :: ConfigData -- ^ Cluster config
63 -> Qlang.ItemType -- ^ Query type
64 -> [Either String Integer] -- ^ Requested names
66 -> [String] -- ^ Requested fields
67 -> Bool -- ^ Whether to do sync queries or not
68 -> IO (GenericResult GanetiException JSValue)
69 handleClassicQuery _ _ _ _ True =
70 return . Bad $ OpPrereqError "Sync queries are not allowed" ECodeInval
71 handleClassicQuery cfg qkind names fields _ = do
72 let flt = makeSimpleFilter (nameField qkind) names
73 qr <- query cfg True (Qlang.Query qkind fields flt)
74 return $ showJSON <$> (qr >>= queryCompat)
76 -- | Minimal wrapper to handle the missing config case.
77 handleCallWrapper :: Result ConfigData -> LuxiOp -> IO (ErrorResult JSValue)
78 handleCallWrapper (Bad msg) _ =
79 return . Bad . ConfigurationError $
80 "I do not have access to a valid configuration, cannot\
81 \ process queries: " ++ msg
82 handleCallWrapper (Ok config) op = handleCall config op
84 -- | Actual luxi operation handler.
85 handleCall :: ConfigData -> LuxiOp -> IO (ErrorResult JSValue)
86 handleCall cdata QueryClusterInfo =
87 let cluster = configCluster cdata
88 master = QCluster.clusterMasterNodeName cdata
89 hypervisors = clusterEnabledHypervisors cluster
90 diskTemplates = clusterEnabledDiskTemplates cluster
91 def_hv = case hypervisors of
94 bits = show (bitSize (0::Int)) ++ "bits"
95 arch_tuple = [bits, arch]
96 obj = [ ("software_version", showJSON C.releaseVersion)
97 , ("protocol_version", showJSON C.protocolVersion)
98 , ("config_version", showJSON C.configVersion)
99 , ("os_api_version", showJSON $ maximum C.osApiVersions)
100 , ("export_version", showJSON C.exportVersion)
101 , ("architecture", showJSON arch_tuple)
102 , ("name", showJSON $ clusterClusterName cluster)
103 , ("master", showJSON (case master of
106 , ("default_hypervisor", def_hv)
107 , ("enabled_hypervisors", showJSON hypervisors)
108 , ("hvparams", showJSON $ clusterHvparams cluster)
109 , ("os_hvp", showJSON $ clusterOsHvp cluster)
110 , ("beparams", showJSON $ clusterBeparams cluster)
111 , ("osparams", showJSON $ clusterOsparams cluster)
112 , ("ipolicy", showJSON $ clusterIpolicy cluster)
113 , ("nicparams", showJSON $ clusterNicparams cluster)
114 , ("ndparams", showJSON $ clusterNdparams cluster)
115 , ("diskparams", showJSON $ clusterDiskparams cluster)
116 , ("candidate_pool_size",
117 showJSON $ clusterCandidatePoolSize cluster)
118 , ("master_netdev", showJSON $ clusterMasterNetdev cluster)
119 , ("master_netmask", showJSON $ clusterMasterNetmask cluster)
120 , ("use_external_mip_script",
121 showJSON $ clusterUseExternalMipScript cluster)
122 , ("volume_group_name",
123 maybe JSNull showJSON (clusterVolumeGroupName cluster))
124 , ("drbd_usermode_helper",
125 maybe JSNull showJSON (clusterDrbdUsermodeHelper cluster))
126 , ("file_storage_dir", showJSON $ clusterFileStorageDir cluster)
127 , ("shared_file_storage_dir",
128 showJSON $ clusterSharedFileStorageDir cluster)
129 , ("maintain_node_health",
130 showJSON $ clusterMaintainNodeHealth cluster)
131 , ("ctime", showJSON $ clusterCtime cluster)
132 , ("mtime", showJSON $ clusterMtime cluster)
133 , ("uuid", showJSON $ clusterUuid cluster)
134 , ("tags", showJSON $ clusterTags cluster)
135 , ("uid_pool", showJSON $ clusterUidPool cluster)
136 , ("default_iallocator",
137 showJSON $ clusterDefaultIallocator cluster)
138 , ("reserved_lvs", showJSON $ clusterReservedLvs cluster)
139 , ("primary_ip_version",
140 showJSON . ipFamilyToVersion $ clusterPrimaryIpFamily cluster)
141 , ("prealloc_wipe_disks",
142 showJSON $ clusterPreallocWipeDisks cluster)
143 , ("hidden_os", showJSON $ clusterHiddenOs cluster)
144 , ("blacklisted_os", showJSON $ clusterBlacklistedOs cluster)
145 , ("enabled_disk_templates", showJSON diskTemplates)
149 Ok _ -> return . Ok . J.makeObj $ obj
150 Bad ex -> return $ Bad ex
152 handleCall cfg (QueryTags kind) =
153 let tags = case kind of
154 TagCluster -> Ok . clusterTags $ configCluster cfg
155 TagGroup name -> groupTags <$> Config.getGroup cfg name
156 TagNode name -> nodeTags <$> Config.getNode cfg name
157 TagInstance name -> instTags <$> Config.getInstance cfg name
158 in return (J.showJSON <$> tags)
160 handleCall cfg (Query qkind qfields qfilter) = do
161 result <- query cfg True (Qlang.Query qkind qfields qfilter)
162 return $ J.showJSON <$> result
164 handleCall _ (QueryFields qkind qfields) = do
165 let result = queryFields (Qlang.QueryFields qkind qfields)
166 return $ J.showJSON <$> result
168 handleCall cfg (QueryNodes names fields lock) =
169 handleClassicQuery cfg (Qlang.ItemTypeOpCode Qlang.QRNode)
170 (map Left names) fields lock
172 handleCall cfg (QueryGroups names fields lock) =
173 handleClassicQuery cfg (Qlang.ItemTypeOpCode Qlang.QRGroup)
174 (map Left names) fields lock
176 handleCall cfg (QueryJobs names fields) =
177 handleClassicQuery cfg (Qlang.ItemTypeLuxi Qlang.QRJob)
178 (map (Right . fromIntegral . fromJobId) names) fields False
180 handleCall cfg (QueryNetworks names fields lock) =
181 handleClassicQuery cfg (Qlang.ItemTypeOpCode Qlang.QRNetwork)
182 (map Left names) fields lock
186 GenericError ("Luxi call '" ++ strOfOp op ++ "' not implemented")
188 -- | Given a decoded luxi request, executes it and sends the luxi
189 -- response back to the client.
190 handleClientMsg :: Client -> ConfigReader -> LuxiOp -> IO Bool
191 handleClientMsg client creader args = do
193 logDebug $ "Request: " ++ show args
194 call_result <- handleCallWrapper cfg args
198 logWarning $ "Failed to execute request " ++ show args ++ ": "
200 return (False, showJSON err)
202 -- only log the first 2,000 chars of the result
203 logDebug $ "Result (truncated): " ++ take 2000 (J.encode result)
204 logInfo $ "Successfully handled " ++ strOfOp args
205 return (True, result)
206 sendMsg client $ buildResponse status rval
209 -- | Handles one iteration of the client protocol: receives message,
210 -- checks it for validity and decodes it, returns response.
211 handleClient :: Client -> ConfigReader -> IO Bool
212 handleClient client creader = do
213 !msg <- recvMsgExt client
214 logDebug $ "Received message: " ++ show msg
216 RecvConnClosed -> logDebug "Connection closed" >> return False
217 RecvError err -> logWarning ("Error during message receiving: " ++ err) >>
220 case validateCall payload >>= decodeCall of
222 let errmsg = "Failed to parse request: " ++ err
224 sendMsg client $ buildResponse False (showJSON errmsg)
226 Ok args -> handleClientMsg client creader args
228 -- | Main client loop: runs one loop of 'handleClient', and if that
229 -- doesn't report a finished (closed) connection, restarts itself.
230 clientLoop :: Client -> ConfigReader -> IO ()
231 clientLoop client creader = do
232 result <- handleClient client creader
234 then clientLoop client creader
235 else closeClient client
237 -- | Main listener loop: accepts clients, forks an I/O thread to handle
239 listener :: ConfigReader -> S.Socket -> IO ()
240 listener creader socket = do
241 client <- acceptClient socket
242 _ <- forkIO $ clientLoop client creader
245 -- | Type alias for prepMain results
246 type PrepResult = (FilePath, S.Socket, IORef (Result ConfigData))
248 -- | Check function for luxid.
249 checkMain :: CheckFn ()
250 checkMain _ = return $ Right ()
252 -- | Prepare function for luxid.
253 prepMain :: PrepFn () PrepResult
255 socket_path <- Path.defaultQuerySocket
256 cleanupSocket socket_path
257 s <- describeError "binding to the Luxi socket"
258 Nothing (Just socket_path) $ getServer True socket_path
259 cref <- newIORef (Bad "Configuration not yet loaded")
260 return (socket_path, s, cref)
263 main :: MainFn () PrepResult
264 main _ _ (socket_path, server, cref) = do
265 initConfigReader id cref
266 let creader = readIORef cref
269 (forever $ listener creader server)
270 (closeServer socket_path server)