Statistics
| Branch: | Tag: | Revision:

root / src / Ganeti / Luxi.hs @ 71a4c605

History | View | Annotate | Download (11.4 kB)

1
{-# LANGUAGE TemplateHaskell #-}
2

    
3
{-| Implementation of the Ganeti LUXI interface.
4

    
5
-}
6

    
7
{-
8

    
9
Copyright (C) 2009, 2010, 2011, 2012, 2013 Google Inc.
10

    
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.
15

    
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.
20

    
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
24
02110-1301, USA.
25

    
26
-}
27

    
28
module Ganeti.Luxi
29
  ( LuxiOp(..)
30
  , LuxiReq(..)
31
  , Client
32
  , JobId
33
  , fromJobId
34
  , makeJobId
35
  , RecvResult(..)
36
  , strOfOp
37
  , getClient
38
  , getServer
39
  , acceptClient
40
  , closeClient
41
  , closeServer
42
  , callMethod
43
  , submitManyJobs
44
  , queryJobsStatus
45
  , buildCall
46
  , buildResponse
47
  , validateCall
48
  , decodeCall
49
  , recvMsg
50
  , recvMsgExt
51
  , sendMsg
52
  , allLuxiCalls
53
  ) where
54

    
55
import Control.Monad
56
import qualified Data.ByteString.UTF8 as UTF8
57
import Text.JSON (encodeStrict, decodeStrict)
58
import qualified Text.JSON as J
59
import Text.JSON.Pretty (pp_value)
60
import Text.JSON.Types
61

    
62
import Ganeti.BasicTypes
63
import Ganeti.Constants
64
import Ganeti.Errors
65
import Ganeti.JSON
66
import Ganeti.UDSServer
67
import Ganeti.OpParams (pTagsObject)
68
import Ganeti.OpCodes
69
import qualified Ganeti.Query.Language as Qlang
70
import Ganeti.THH
71
import Ganeti.Types
72

    
73

    
74
-- | Currently supported Luxi operations and JSON serialization.
75
$(genLuxiOp "LuxiOp"
76
  [ (luxiReqQuery,
77
    [ simpleField "what"    [t| Qlang.ItemType |]
78
    , simpleField "fields"  [t| [String]  |]
79
    , simpleField "qfilter" [t| Qlang.Filter Qlang.FilterField |]
80
    ])
81
  , (luxiReqQueryFields,
82
    [ simpleField "what"    [t| Qlang.ItemType |]
83
    , simpleField "fields"  [t| [String]  |]
84
    ])
85
  , (luxiReqQueryNodes,
86
     [ simpleField "names"  [t| [String] |]
87
     , simpleField "fields" [t| [String] |]
88
     , simpleField "lock"   [t| Bool     |]
89
     ])
90
  , (luxiReqQueryGroups,
91
     [ simpleField "names"  [t| [String] |]
92
     , simpleField "fields" [t| [String] |]
93
     , simpleField "lock"   [t| Bool     |]
94
     ])
95
  , (luxiReqQueryNetworks,
96
     [ simpleField "names"  [t| [String] |]
97
     , simpleField "fields" [t| [String] |]
98
     , simpleField "lock"   [t| Bool     |]
99
     ])
100
  , (luxiReqQueryInstances,
101
     [ simpleField "names"  [t| [String] |]
102
     , simpleField "fields" [t| [String] |]
103
     , simpleField "lock"   [t| Bool     |]
104
     ])
105
  , (luxiReqQueryJobs,
106
     [ simpleField "ids"    [t| [JobId]  |]
107
     , simpleField "fields" [t| [String] |]
108
     ])
109
  , (luxiReqQueryExports,
110
     [ simpleField "nodes" [t| [String] |]
111
     , simpleField "lock"  [t| Bool     |]
112
     ])
113
  , (luxiReqQueryConfigValues,
114
     [ simpleField "fields" [t| [String] |] ]
115
    )
116
  , (luxiReqQueryClusterInfo, [])
117
  , (luxiReqQueryTags,
118
     [ pTagsObject 
119
     , simpleField "name" [t| String |]
120
     ])
121
  , (luxiReqSubmitJob,
122
     [ simpleField "job" [t| [MetaOpCode] |] ]
123
    )
124
  , (luxiReqSubmitJobToDrainedQueue,
125
     [ simpleField "job" [t| [MetaOpCode] |] ]
126
    )
127
  , (luxiReqSubmitManyJobs,
128
     [ simpleField "ops" [t| [[MetaOpCode]] |] ]
129
    )
130
  , (luxiReqWaitForJobChange,
131
     [ simpleField "job"      [t| JobId   |]
132
     , simpleField "fields"   [t| [String]|]
133
     , simpleField "prev_job" [t| JSValue |]
134
     , simpleField "prev_log" [t| JSValue |]
135
     , simpleField "tmout"    [t| Int     |]
136
     ])
137
  , (luxiReqPickupJob,
138
     [ simpleField "job" [t| JobId |] ]
139
    )
140
  , (luxiReqArchiveJob,
141
     [ simpleField "job" [t| JobId |] ]
142
    )
143
  , (luxiReqAutoArchiveJobs,
144
     [ simpleField "age"   [t| Int |]
145
     , simpleField "tmout" [t| Int |]
146
     ])
147
  , (luxiReqCancelJob,
148
     [ simpleField "job" [t| JobId |] ]
149
    )
150
  , (luxiReqChangeJobPriority,
151
     [ simpleField "job"      [t| JobId |]
152
     , simpleField "priority" [t| Int |] ]
153
    )
154
  , (luxiReqSetDrainFlag,
155
     [ simpleField "flag" [t| Bool |] ]
156
    )
157
  , (luxiReqSetWatcherPause,
158
     [ simpleField "duration" [t| Double |] ]
159
    )
160
  ])
161

    
162
$(makeJSONInstance ''LuxiReq)
163

    
164
-- | List of all defined Luxi calls.
165
$(genAllConstr (drop 3) ''LuxiReq "allLuxiCalls")
166

    
167
-- | The serialisation of LuxiOps into strings in messages.
168
$(genStrOfOp ''LuxiOp "strOfOp")
169

    
170
-- | Type holding the initial (unparsed) Luxi call.
171
data LuxiCall = LuxiCall LuxiReq JSValue
172

    
173
-- | Serialize a request to String.
174
buildCall :: LuxiOp  -- ^ The method
175
          -> String  -- ^ The serialized form
176
buildCall lo =
177
  let ja = [ (strOfKey Method, J.showJSON $ strOfOp lo)
178
           , (strOfKey Args, opToArgs lo)
179
           ]
180
      jo = toJSObject ja
181
  in encodeStrict jo
182

    
183
-- | Check that luxi request contains the required keys and parse it.
184
validateCall :: String -> Result LuxiCall
185
validateCall s = do
186
  arr <- fromJResult "parsing top-level luxi message" $
187
         decodeStrict s::Result (JSObject JSValue)
188
  let aobj = fromJSObject arr
189
  call <- fromObj aobj (strOfKey Method)::Result LuxiReq
190
  args <- fromObj aobj (strOfKey Args)
191
  return (LuxiCall call args)
192

    
193
-- | Converts Luxi call arguments into a 'LuxiOp' data structure.
194
--
195
-- This is currently hand-coded until we make it more uniform so that
196
-- it can be generated using TH.
197
decodeCall :: LuxiCall -> Result LuxiOp
198
decodeCall (LuxiCall call args) =
199
  case call of
200
    ReqQueryJobs -> do
201
              (jids, jargs) <- fromJVal args
202
              jids' <- case jids of
203
                         JSNull -> return []
204
                         _ -> fromJVal jids
205
              return $ QueryJobs jids' jargs
206
    ReqQueryInstances -> do
207
              (names, fields, locking) <- fromJVal args
208
              return $ QueryInstances names fields locking
209
    ReqQueryNodes -> do
210
              (names, fields, locking) <- fromJVal args
211
              return $ QueryNodes names fields locking
212
    ReqQueryGroups -> do
213
              (names, fields, locking) <- fromJVal args
214
              return $ QueryGroups names fields locking
215
    ReqQueryClusterInfo ->
216
              return QueryClusterInfo
217
    ReqQueryNetworks -> do
218
              (names, fields, locking) <- fromJVal args
219
              return $ QueryNetworks names fields locking
220
    ReqQuery -> do
221
              (what, fields, qfilter) <- fromJVal args
222
              return $ Query what fields qfilter
223
    ReqQueryFields -> do
224
              (what, fields) <- fromJVal args
225
              fields' <- case fields of
226
                           JSNull -> return []
227
                           _ -> fromJVal fields
228
              return $ QueryFields what fields'
229
    ReqSubmitJob -> do
230
              [ops1] <- fromJVal args
231
              ops2 <- mapM (fromJResult (luxiReqToRaw call) . J.readJSON) ops1
232
              return $ SubmitJob ops2
233
    ReqSubmitJobToDrainedQueue -> do
234
              [ops1] <- fromJVal args
235
              ops2 <- mapM (fromJResult (luxiReqToRaw call) . J.readJSON) ops1
236
              return $ SubmitJobToDrainedQueue ops2
237
    ReqSubmitManyJobs -> do
238
              [ops1] <- fromJVal args
239
              ops2 <- mapM (fromJResult (luxiReqToRaw call) . J.readJSON) ops1
240
              return $ SubmitManyJobs ops2
241
    ReqWaitForJobChange -> do
242
              (jid, fields, pinfo, pidx, wtmout) <-
243
                -- No instance for 5-tuple, code copied from the
244
                -- json sources and adapted
245
                fromJResult "Parsing WaitForJobChange message" $
246
                case args of
247
                  JSArray [a, b, c, d, e] ->
248
                    (,,,,) `fmap`
249
                    J.readJSON a `ap`
250
                    J.readJSON b `ap`
251
                    J.readJSON c `ap`
252
                    J.readJSON d `ap`
253
                    J.readJSON e
254
                  _ -> J.Error "Not enough values"
255
              return $ WaitForJobChange jid fields pinfo pidx wtmout
256
    ReqPickupJob -> do
257
              [jid] <- fromJVal args
258
              return $ PickupJob jid
259
    ReqArchiveJob -> do
260
              [jid] <- fromJVal args
261
              return $ ArchiveJob jid
262
    ReqAutoArchiveJobs -> do
263
              (age, tmout) <- fromJVal args
264
              return $ AutoArchiveJobs age tmout
265
    ReqQueryExports -> do
266
              (nodes, lock) <- fromJVal args
267
              return $ QueryExports nodes lock
268
    ReqQueryConfigValues -> do
269
              [fields] <- fromJVal args
270
              return $ QueryConfigValues fields
271
    ReqQueryTags -> do
272
              (kind, name) <- fromJVal args
273
              return $ QueryTags kind name
274
    ReqCancelJob -> do
275
              [jid] <- fromJVal args
276
              return $ CancelJob jid
277
    ReqChangeJobPriority -> do
278
              (jid, priority) <- fromJVal args
279
              return $ ChangeJobPriority jid priority
280
    ReqSetDrainFlag -> do
281
              [flag] <- fromJVal args
282
              return $ SetDrainFlag flag
283
    ReqSetWatcherPause -> do
284
              [duration] <- fromJVal args
285
              return $ SetWatcherPause duration
286

    
287
-- | Check that luxi responses contain the required keys and that the
288
-- call was successful.
289
validateResult :: String -> ErrorResult JSValue
290
validateResult s = do
291
  when (UTF8.replacement_char `elem` s) $
292
       fail "Failed to decode UTF-8, detected replacement char after decoding"
293
  oarr <- fromJResult "Parsing LUXI response" (decodeStrict s)
294
  let arr = J.fromJSObject oarr
295
  status <- fromObj arr (strOfKey Success)
296
  result <- fromObj arr (strOfKey Result)
297
  if status
298
    then return result
299
    else decodeError result
300

    
301
-- | Try to decode an error from the server response. This function
302
-- will always fail, since it's called only on the error path (when
303
-- status is False).
304
decodeError :: JSValue -> ErrorResult JSValue
305
decodeError val =
306
  case fromJVal val of
307
    Ok e -> Bad e
308
    Bad msg -> Bad $ GenericError msg
309

    
310
-- | Generic luxi method call.
311
callMethod :: LuxiOp -> Client -> IO (ErrorResult JSValue)
312
callMethod method s = do
313
  sendMsg s $ buildCall method
314
  result <- recvMsg s
315
  let rval = validateResult result
316
  return rval
317

    
318
-- | Parse job submission result.
319
parseSubmitJobResult :: JSValue -> ErrorResult JobId
320
parseSubmitJobResult (JSArray [JSBool True, v]) =
321
  case J.readJSON v of
322
    J.Error msg -> Bad $ LuxiError msg
323
    J.Ok v' -> Ok v'
324
parseSubmitJobResult (JSArray [JSBool False, JSString x]) =
325
  Bad . LuxiError $ fromJSString x
326
parseSubmitJobResult v =
327
  Bad . LuxiError $ "Unknown result from the master daemon: " ++
328
      show (pp_value v)
329

    
330
-- | Specialized submitManyJobs call.
331
submitManyJobs :: Client -> [[MetaOpCode]] -> IO (ErrorResult [JobId])
332
submitManyJobs s jobs = do
333
  rval <- callMethod (SubmitManyJobs jobs) s
334
  -- map each result (status, payload) pair into a nice Result ADT
335
  return $ case rval of
336
             Bad x -> Bad x
337
             Ok (JSArray r) -> mapM parseSubmitJobResult r
338
             x -> Bad . LuxiError $
339
                  "Cannot parse response from Ganeti: " ++ show x
340

    
341
-- | Custom queryJobs call.
342
queryJobsStatus :: Client -> [JobId] -> IO (ErrorResult [JobStatus])
343
queryJobsStatus s jids = do
344
  rval <- callMethod (QueryJobs jids ["status"]) s
345
  return $ case rval of
346
             Bad x -> Bad x
347
             Ok y -> case J.readJSON y::(J.Result [[JobStatus]]) of
348
                       J.Ok vals -> if any null vals
349
                                    then Bad $
350
                                         LuxiError "Missing job status field"
351
                                    else Ok (map head vals)
352
                       J.Error x -> Bad $ LuxiError x