Statistics
| Branch: | Tag: | Revision:

root / test / hs / Test / Ganeti / OpCodes.hs @ 66af5ec5

History | View | Annotate | Download (24.1 kB)

1
{-# LANGUAGE TemplateHaskell #-}
2
{-# OPTIONS_GHC -fno-warn-orphans #-}
3

    
4
{-| Unittests for ganeti-htools.
5

    
6
-}
7

    
8
{-
9

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

    
12
This program is free software; you can redistribute it and/or modify
13
it under the terms of the GNU General Public License as published by
14
the Free Software Foundation; either version 2 of the License, or
15
(at your option) any later version.
16

    
17
This program is distributed in the hope that it will be useful, but
18
WITHOUT ANY WARRANTY; without even the implied warranty of
19
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20
General Public License for more details.
21

    
22
You should have received a copy of the GNU General Public License
23
along with this program; if not, write to the Free Software
24
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
25
02110-1301, USA.
26

    
27
-}
28

    
29
module Test.Ganeti.OpCodes
30
  ( testOpCodes
31
  , OpCodes.OpCode(..)
32
  ) where
33

    
34
import Test.HUnit as HUnit
35
import Test.QuickCheck as QuickCheck
36

    
37
import Control.Applicative
38
import Control.Monad
39
import Data.Char
40
import Data.List
41
import qualified Data.Map as Map
42
import qualified Text.JSON as J
43
import Text.Printf (printf)
44

    
45
import Test.Ganeti.TestHelper
46
import Test.Ganeti.TestCommon
47
import Test.Ganeti.Types ()
48
import Test.Ganeti.Query.Language
49

    
50
import Ganeti.BasicTypes
51
import qualified Ganeti.Constants as C
52
import qualified Ganeti.OpCodes as OpCodes
53
import Ganeti.Types
54
import Ganeti.OpParams
55
import Ganeti.JSON
56

    
57
{-# ANN module "HLint: ignore Use camelCase" #-}
58

    
59
-- * Arbitrary instances
60

    
61
instance Arbitrary OpCodes.TagObject where
62
  arbitrary = oneof [ OpCodes.TagInstance <$> genFQDN
63
                    , OpCodes.TagNode     <$> genFQDN
64
                    , OpCodes.TagGroup    <$> genFQDN
65
                    , pure OpCodes.TagCluster
66
                    ]
67

    
68
$(genArbitrary ''OpCodes.ReplaceDisksMode)
69

    
70
$(genArbitrary ''DiskAccess)
71

    
72
$(genArbitrary ''InstReasonSrc)
73

    
74
instance Arbitrary OpCodes.DiskIndex where
75
  arbitrary = choose (0, C.maxDisks - 1) >>= OpCodes.mkDiskIndex
76

    
77
instance Arbitrary INicParams where
78
  arbitrary = INicParams <$> genMaybe genNameNE <*> genMaybe genName <*>
79
              genMaybe genNameNE <*> genMaybe genNameNE
80

    
81
instance Arbitrary IDiskParams where
82
  arbitrary = IDiskParams <$> arbitrary <*> arbitrary <*>
83
              genMaybe genNameNE <*> genMaybe genNameNE <*>
84
              genMaybe genNameNE
85

    
86
instance Arbitrary RecreateDisksInfo where
87
  arbitrary = oneof [ pure RecreateDisksAll
88
                    , RecreateDisksIndices <$> arbitrary
89
                    , RecreateDisksParams <$> arbitrary
90
                    ]
91

    
92
instance Arbitrary DdmOldChanges where
93
  arbitrary = oneof [ DdmOldIndex <$> arbitrary
94
                    , DdmOldMod   <$> arbitrary
95
                    ]
96

    
97
instance (Arbitrary a) => Arbitrary (SetParamsMods a) where
98
  arbitrary = oneof [ pure SetParamsEmpty
99
                    , SetParamsDeprecated <$> arbitrary
100
                    , SetParamsNew        <$> arbitrary
101
                    ]
102

    
103
instance Arbitrary ExportTarget where
104
  arbitrary = oneof [ ExportTargetLocal <$> genNodeNameNE
105
                    , ExportTargetRemote <$> pure []
106
                    ]
107

    
108
instance Arbitrary OpCodes.OpCode where
109
  arbitrary = do
110
    op_id <- elements OpCodes.allOpIDs
111
    case op_id of
112
      "OP_TEST_DELAY" ->
113
        OpCodes.OpTestDelay <$> arbitrary <*> arbitrary <*>
114
          genNodeNamesNE <*> arbitrary
115
      "OP_INSTANCE_REPLACE_DISKS" ->
116
        OpCodes.OpInstanceReplaceDisks <$> genFQDN <*> arbitrary <*>
117
          arbitrary <*> arbitrary <*> genDiskIndices <*>
118
          genMaybe genNodeNameNE <*> genMaybe genNameNE
119
      "OP_INSTANCE_FAILOVER" ->
120
        OpCodes.OpInstanceFailover <$> genFQDN <*> arbitrary <*> arbitrary <*>
121
          genMaybe genNodeNameNE <*> arbitrary <*> genMaybe genNameNE
122
      "OP_INSTANCE_MIGRATE" ->
123
        OpCodes.OpInstanceMigrate <$> genFQDN <*> arbitrary <*> arbitrary <*>
124
          genMaybe genNodeNameNE <*> arbitrary <*>
125
          arbitrary <*> arbitrary <*> genMaybe genNameNE <*> arbitrary
126
      "OP_TAGS_GET" ->
127
        OpCodes.OpTagsGet <$> arbitrary <*> arbitrary
128
      "OP_TAGS_SEARCH" ->
129
        OpCodes.OpTagsSearch <$> genNameNE
130
      "OP_TAGS_SET" ->
131
        OpCodes.OpTagsSet <$> arbitrary <*> genTags
132
      "OP_TAGS_DEL" ->
133
        OpCodes.OpTagsSet <$> arbitrary <*> genTags
134
      "OP_CLUSTER_POST_INIT" -> pure OpCodes.OpClusterPostInit
135
      "OP_CLUSTER_DESTROY" -> pure OpCodes.OpClusterDestroy
136
      "OP_CLUSTER_QUERY" -> pure OpCodes.OpClusterQuery
137
      "OP_CLUSTER_VERIFY" ->
138
        OpCodes.OpClusterVerify <$> arbitrary <*> arbitrary <*>
139
          genSet Nothing <*> genSet Nothing <*> arbitrary <*>
140
          genMaybe genNameNE
141
      "OP_CLUSTER_VERIFY_CONFIG" ->
142
        OpCodes.OpClusterVerifyConfig <$> arbitrary <*> arbitrary <*>
143
          genSet Nothing <*> arbitrary
144
      "OP_CLUSTER_VERIFY_GROUP" ->
145
        OpCodes.OpClusterVerifyGroup <$> genNameNE <*> arbitrary <*>
146
          arbitrary <*> genSet Nothing <*> genSet Nothing <*> arbitrary
147
      "OP_CLUSTER_VERIFY_DISKS" -> pure OpCodes.OpClusterVerifyDisks
148
      "OP_GROUP_VERIFY_DISKS" ->
149
        OpCodes.OpGroupVerifyDisks <$> genNameNE
150
      "OP_CLUSTER_REPAIR_DISK_SIZES" ->
151
        OpCodes.OpClusterRepairDiskSizes <$> genNodeNamesNE
152
      "OP_CLUSTER_CONFIG_QUERY" ->
153
        OpCodes.OpClusterConfigQuery <$> genFieldsNE
154
      "OP_CLUSTER_RENAME" ->
155
        OpCodes.OpClusterRename <$> genNameNE
156
      "OP_CLUSTER_SET_PARAMS" ->
157
        OpCodes.OpClusterSetParams <$> emptyMUD <*> emptyMUD <*>
158
          arbitrary <*> genMaybe (listOf1 arbitrary >>= mkNonEmpty) <*>
159
          genMaybe genEmptyContainer <*> emptyMUD <*>
160
          genMaybe genEmptyContainer <*> genMaybe genEmptyContainer <*>
161
          genMaybe genEmptyContainer <*> genMaybe arbitrary <*>
162
          arbitrary <*> arbitrary <*> arbitrary <*>
163
          arbitrary <*> arbitrary <*> arbitrary <*>
164
          emptyMUD <*> emptyMUD <*> arbitrary <*>
165
          arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*>
166
          arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*>
167
          arbitrary
168
      "OP_CLUSTER_REDIST_CONF" -> pure OpCodes.OpClusterRedistConf
169
      "OP_CLUSTER_ACTIVATE_MASTER_IP" ->
170
        pure OpCodes.OpClusterActivateMasterIp
171
      "OP_CLUSTER_DEACTIVATE_MASTER_IP" ->
172
        pure OpCodes.OpClusterDeactivateMasterIp
173
      "OP_QUERY" ->
174
        OpCodes.OpQuery <$> arbitrary <*> arbitrary <*> arbitrary <*> genFilter
175
      "OP_QUERY_FIELDS" ->
176
        OpCodes.OpQueryFields <$> arbitrary <*> arbitrary
177
      "OP_OOB_COMMAND" ->
178
        OpCodes.OpOobCommand <$> genNodeNamesNE <*> arbitrary <*>
179
          arbitrary <*> arbitrary <*> (arbitrary `suchThat` (>0))
180
      "OP_NODE_REMOVE" -> OpCodes.OpNodeRemove <$> genNodeNameNE
181
      "OP_NODE_ADD" ->
182
        OpCodes.OpNodeAdd <$> genNodeNameNE <*> emptyMUD <*> emptyMUD <*>
183
          genMaybe genName <*> genMaybe genNameNE <*> arbitrary <*>
184
          genMaybe genNameNE <*> arbitrary <*> arbitrary <*> emptyMUD
185
      "OP_NODE_QUERY" ->
186
        OpCodes.OpNodeQuery <$> genFieldsNE <*> genNamesNE <*> arbitrary
187
      "OP_NODE_QUERYVOLS" ->
188
        OpCodes.OpNodeQueryvols <$> arbitrary <*> genNodeNamesNE
189
      "OP_NODE_QUERY_STORAGE" ->
190
        OpCodes.OpNodeQueryStorage <$> arbitrary <*> arbitrary <*>
191
          genNodeNamesNE <*> genNameNE
192
      "OP_NODE_MODIFY_STORAGE" ->
193
        OpCodes.OpNodeModifyStorage <$> genNodeNameNE <*> arbitrary <*>
194
          genNameNE <*> pure emptyJSObject
195
      "OP_REPAIR_NODE_STORAGE" ->
196
        OpCodes.OpRepairNodeStorage <$> genNodeNameNE <*> arbitrary <*>
197
          genNameNE <*> arbitrary
198
      "OP_NODE_SET_PARAMS" ->
199
        OpCodes.OpNodeSetParams <$> genNodeNameNE <*> arbitrary <*>
200
          emptyMUD <*> emptyMUD <*> arbitrary <*> arbitrary <*> arbitrary <*>
201
          arbitrary <*> arbitrary <*> arbitrary <*> genMaybe genNameNE <*>
202
          emptyMUD <*> arbitrary
203
      "OP_NODE_POWERCYCLE" ->
204
        OpCodes.OpNodePowercycle <$> genNodeNameNE <*> arbitrary
205
      "OP_NODE_MIGRATE" ->
206
        OpCodes.OpNodeMigrate <$> genNodeNameNE <*> arbitrary <*>
207
          arbitrary <*> genMaybe genNodeNameNE <*> arbitrary <*>
208
          arbitrary <*> genMaybe genNameNE
209
      "OP_NODE_EVACUATE" ->
210
        OpCodes.OpNodeEvacuate <$> arbitrary <*> genNodeNameNE <*>
211
          genMaybe genNodeNameNE <*> genMaybe genNameNE <*> arbitrary
212
      "OP_INSTANCE_CREATE" ->
213
        OpCodes.OpInstanceCreate <$> genFQDN <*> arbitrary <*>
214
          arbitrary <*> arbitrary <*> arbitrary <*> pure emptyJSObject <*>
215
          arbitrary <*> arbitrary <*> arbitrary <*> genMaybe genNameNE <*>
216
          pure emptyJSObject <*> arbitrary <*> genMaybe genNameNE <*>
217
          arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*>
218
          arbitrary <*> arbitrary <*> pure emptyJSObject <*>
219
          genMaybe genNameNE <*>
220
          genMaybe genNodeNameNE <*> genMaybe genNodeNameNE <*>
221
          genMaybe (pure []) <*> genMaybe genNodeNameNE <*>
222
          arbitrary <*> genMaybe genNodeNameNE <*>
223
          genMaybe genNodeNameNE <*> genMaybe genNameNE <*>
224
          arbitrary <*> arbitrary <*> (genTags >>= mapM mkNonEmpty)
225
      "OP_INSTANCE_MULTI_ALLOC" ->
226
        OpCodes.OpInstanceMultiAlloc <$> genMaybe genNameNE <*> pure [] <*>
227
          arbitrary
228
      "OP_INSTANCE_REINSTALL" ->
229
        OpCodes.OpInstanceReinstall <$> genFQDN <*> arbitrary <*>
230
          genMaybe genNameNE <*> genMaybe (pure emptyJSObject)
231
      "OP_INSTANCE_REMOVE" ->
232
        OpCodes.OpInstanceRemove <$> genFQDN <*> arbitrary <*> arbitrary
233
      "OP_INSTANCE_RENAME" ->
234
        OpCodes.OpInstanceRename <$> genFQDN <*> genNodeNameNE <*>
235
          arbitrary <*> arbitrary
236
      "OP_INSTANCE_STARTUP" ->
237
        OpCodes.OpInstanceStartup <$> genFQDN <*> arbitrary <*> arbitrary <*>
238
          pure emptyJSObject <*> pure emptyJSObject <*>
239
          arbitrary <*> arbitrary
240
      "OP_INSTANCE_SHUTDOWN" ->
241
        OpCodes.OpInstanceShutdown <$> genFQDN <*> arbitrary <*> arbitrary <*>
242
          arbitrary <*> arbitrary
243
      "OP_INSTANCE_REBOOT" ->
244
        OpCodes.OpInstanceReboot <$> genFQDN <*> arbitrary <*>
245
          arbitrary <*> arbitrary <*> ((,) <$> arbitrary <*> genStringNE)
246
      "OP_INSTANCE_MOVE" ->
247
        OpCodes.OpInstanceMove <$> genFQDN <*> arbitrary <*> arbitrary <*>
248
          genNodeNameNE <*> arbitrary
249
      "OP_INSTANCE_CONSOLE" -> OpCodes.OpInstanceConsole <$> genFQDN
250
      "OP_INSTANCE_ACTIVATE_DISKS" ->
251
        OpCodes.OpInstanceActivateDisks <$> genFQDN <*>
252
          arbitrary <*> arbitrary
253
      "OP_INSTANCE_DEACTIVATE_DISKS" ->
254
        OpCodes.OpInstanceDeactivateDisks <$> genFQDN <*> arbitrary
255
      "OP_INSTANCE_RECREATE_DISKS" ->
256
        OpCodes.OpInstanceRecreateDisks <$> genFQDN <*> arbitrary <*>
257
          genNodeNamesNE <*> genMaybe genNameNE
258
      "OP_INSTANCE_QUERY" ->
259
        OpCodes.OpInstanceQuery <$> genFieldsNE <*> genNamesNE <*> arbitrary
260
      "OP_INSTANCE_QUERY_DATA" ->
261
        OpCodes.OpInstanceQueryData <$> arbitrary <*>
262
          genNodeNamesNE <*> arbitrary
263
      "OP_INSTANCE_SET_PARAMS" ->
264
        OpCodes.OpInstanceSetParams <$> genFQDN <*> arbitrary <*>
265
          arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*>
266
          pure emptyJSObject <*> arbitrary <*> pure emptyJSObject <*>
267
          arbitrary <*> genMaybe genNodeNameNE <*> genMaybe genNodeNameNE <*>
268
          genMaybe genNameNE <*> pure emptyJSObject <*> arbitrary <*>
269
          arbitrary <*> arbitrary
270
      "OP_INSTANCE_GROW_DISK" ->
271
        OpCodes.OpInstanceGrowDisk <$> genFQDN <*> arbitrary <*>
272
          arbitrary <*> arbitrary <*> arbitrary
273
      "OP_INSTANCE_CHANGE_GROUP" ->
274
        OpCodes.OpInstanceChangeGroup <$> genFQDN <*> arbitrary <*>
275
          genMaybe genNameNE <*> genMaybe (resize maxNodes (listOf genNameNE))
276
      "OP_GROUP_ADD" ->
277
        OpCodes.OpGroupAdd <$> genNameNE <*> arbitrary <*>
278
          emptyMUD <*> genMaybe genEmptyContainer <*>
279
          emptyMUD <*> emptyMUD <*> emptyMUD
280
      "OP_GROUP_ASSIGN_NODES" ->
281
        OpCodes.OpGroupAssignNodes <$> genNameNE <*> arbitrary <*>
282
          genNodeNamesNE
283
      "OP_GROUP_QUERY" ->
284
        OpCodes.OpGroupQuery <$> genFieldsNE <*> genNamesNE
285
      "OP_GROUP_SET_PARAMS" ->
286
        OpCodes.OpGroupSetParams <$> genNameNE <*> arbitrary <*>
287
          emptyMUD <*> genMaybe genEmptyContainer <*>
288
          emptyMUD <*> emptyMUD <*> emptyMUD
289
      "OP_GROUP_REMOVE" ->
290
        OpCodes.OpGroupRemove <$> genNameNE
291
      "OP_GROUP_RENAME" ->
292
        OpCodes.OpGroupRename <$> genNameNE <*> genNameNE
293
      "OP_GROUP_EVACUATE" ->
294
        OpCodes.OpGroupEvacuate <$> genNameNE <*> arbitrary <*>
295
          genMaybe genNameNE <*> genMaybe genNamesNE
296
      "OP_OS_DIAGNOSE" ->
297
        OpCodes.OpOsDiagnose <$> genFieldsNE <*> genNamesNE
298
      "OP_EXT_STORAGE_DIAGNOSE" ->
299
        OpCodes.OpOsDiagnose <$> genFieldsNE <*> genNamesNE
300
      "OP_BACKUP_QUERY" ->
301
        OpCodes.OpBackupQuery <$> arbitrary <*> genNodeNamesNE
302
      "OP_BACKUP_PREPARE" ->
303
        OpCodes.OpBackupPrepare <$> genFQDN <*> arbitrary
304
      "OP_BACKUP_EXPORT" ->
305
        OpCodes.OpBackupExport <$> genFQDN <*> arbitrary <*>
306
          arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*>
307
          arbitrary <*> genMaybe (pure []) <*> genMaybe genNameNE
308
      "OP_BACKUP_REMOVE" ->
309
        OpCodes.OpBackupRemove <$> genFQDN
310
      "OP_TEST_ALLOCATOR" ->
311
        OpCodes.OpTestAllocator <$> arbitrary <*> arbitrary <*>
312
          genNameNE <*> pure [] <*> pure [] <*>
313
          arbitrary <*> genMaybe genNameNE <*>
314
          (genTags >>= mapM mkNonEmpty) <*>
315
          arbitrary <*> arbitrary <*> genMaybe genNameNE <*>
316
          arbitrary <*> genMaybe genNodeNamesNE <*> arbitrary <*>
317
          genMaybe genNamesNE <*> arbitrary <*> arbitrary
318
      "OP_TEST_JQUEUE" ->
319
        OpCodes.OpTestJqueue <$> arbitrary <*> arbitrary <*>
320
          resize 20 (listOf genFQDN) <*> arbitrary
321
      "OP_TEST_DUMMY" ->
322
        OpCodes.OpTestDummy <$> pure J.JSNull <*> pure J.JSNull <*>
323
          pure J.JSNull <*> pure J.JSNull
324
      "OP_NETWORK_ADD" ->
325
        OpCodes.OpNetworkAdd <$> genNameNE <*> genIp4Net <*>
326
          genMaybe genIp4Addr <*> pure Nothing <*> pure Nothing <*>
327
          genMaybe genMacPrefix <*> genMaybe (listOf genIp4Addr) <*>
328
          arbitrary <*> (genTags >>= mapM mkNonEmpty)
329
      "OP_NETWORK_REMOVE" ->
330
        OpCodes.OpNetworkRemove <$> genNameNE <*> arbitrary
331
      "OP_NETWORK_SET_PARAMS" ->
332
        OpCodes.OpNetworkSetParams <$> genNameNE <*>
333
          genMaybe genIp4Addr <*> pure Nothing <*> pure Nothing <*>
334
          genMaybe genMacPrefix <*> genMaybe (listOf genIp4Addr) <*>
335
          genMaybe (listOf genIp4Addr)
336
      "OP_NETWORK_CONNECT" ->
337
        OpCodes.OpNetworkConnect <$> genNameNE <*> genNameNE <*>
338
          arbitrary <*> genNameNE <*> arbitrary
339
      "OP_NETWORK_DISCONNECT" ->
340
        OpCodes.OpNetworkDisconnect <$> genNameNE <*> genNameNE
341
      "OP_NETWORK_QUERY" ->
342
        OpCodes.OpNetworkQuery <$> genFieldsNE <*> genNamesNE <*> arbitrary
343
      "OP_RESTRICTED_COMMAND" ->
344
        OpCodes.OpRestrictedCommand <$> arbitrary <*> genNodeNamesNE <*>
345
          genNameNE
346
      _ -> fail $ "Undefined arbitrary for opcode " ++ op_id
347

    
348
instance Arbitrary OpCodes.CommonOpParams where
349
  arbitrary = OpCodes.CommonOpParams <$> arbitrary <*> arbitrary <*>
350
                arbitrary <*> resize 5 arbitrary <*> genMaybe genName
351

    
352
-- * Helper functions
353

    
354
-- | Empty JSObject.
355
emptyJSObject :: J.JSObject J.JSValue
356
emptyJSObject = J.toJSObject []
357

    
358
-- | Empty maybe unchecked dictionary.
359
emptyMUD :: Gen (Maybe (J.JSObject J.JSValue))
360
emptyMUD = genMaybe $ pure emptyJSObject
361

    
362
-- | Generates an empty container.
363
genEmptyContainer :: (Ord a) => Gen (GenericContainer a b)
364
genEmptyContainer = pure . GenericContainer $ Map.fromList []
365

    
366
-- | Generates list of disk indices.
367
genDiskIndices :: Gen [DiskIndex]
368
genDiskIndices = do
369
  cnt <- choose (0, C.maxDisks)
370
  genUniquesList cnt arbitrary
371

    
372
-- | Generates a list of node names.
373
genNodeNames :: Gen [String]
374
genNodeNames = resize maxNodes (listOf genFQDN)
375

    
376
-- | Generates a list of node names in non-empty string type.
377
genNodeNamesNE :: Gen [NonEmptyString]
378
genNodeNamesNE = genNodeNames >>= mapM mkNonEmpty
379

    
380
-- | Gets a node name in non-empty type.
381
genNodeNameNE :: Gen NonEmptyString
382
genNodeNameNE = genFQDN >>= mkNonEmpty
383

    
384
-- | Gets a name (non-fqdn) in non-empty type.
385
genNameNE :: Gen NonEmptyString
386
genNameNE = genName >>= mkNonEmpty
387

    
388
-- | Gets a list of names (non-fqdn) in non-empty type.
389
genNamesNE :: Gen [NonEmptyString]
390
genNamesNE = resize maxNodes (listOf genNameNE)
391

    
392
-- | Returns a list of non-empty fields.
393
genFieldsNE :: Gen [NonEmptyString]
394
genFieldsNE = genFields >>= mapM mkNonEmpty
395

    
396
-- | Generate a 3-byte MAC prefix.
397
genMacPrefix :: Gen NonEmptyString
398
genMacPrefix = do
399
  octets <- vectorOf 3 $ choose (0::Int, 255)
400
  mkNonEmpty . intercalate ":" $ map (printf "%02x") octets
401

    
402
-- | Generate a non empty string
403
genStringNE :: Gen NonEmptyString
404
genStringNE = genName >>= mkNonEmpty
405

    
406
-- | Arbitrary instance for MetaOpCode, defined here due to TH ordering.
407
$(genArbitrary ''OpCodes.MetaOpCode)
408

    
409
-- | Small helper to check for a failed JSON deserialisation
410
isJsonError :: J.Result a -> Bool
411
isJsonError (J.Error _) = True
412
isJsonError _           = False
413

    
414
-- * Test cases
415

    
416
-- | Check that opcode serialization is idempotent.
417
prop_serialization :: OpCodes.OpCode -> Property
418
prop_serialization = testSerialisation
419

    
420
-- | Check that Python and Haskell defined the same opcode list.
421
case_AllDefined :: HUnit.Assertion
422
case_AllDefined = do
423
  let py_ops = sort C.opcodesOpIds
424
      hs_ops = sort OpCodes.allOpIDs
425
      extra_py = py_ops \\ hs_ops
426
      extra_hs = hs_ops \\ py_ops
427
  HUnit.assertBool ("Missing OpCodes from the Haskell code:\n" ++
428
                    unlines extra_py) (null extra_py)
429
  HUnit.assertBool ("Extra OpCodes in the Haskell code code:\n" ++
430
                    unlines extra_hs) (null extra_hs)
431

    
432
-- | Custom HUnit test case that forks a Python process and checks
433
-- correspondence between Haskell-generated OpCodes and their Python
434
-- decoded, validated and re-encoded version.
435
--
436
-- Note that we have a strange beast here: since launching Python is
437
-- expensive, we don't do this via a usual QuickProperty, since that's
438
-- slow (I've tested it, and it's indeed quite slow). Rather, we use a
439
-- single HUnit assertion, and in it we manually use QuickCheck to
440
-- generate 500 opcodes times the number of defined opcodes, which
441
-- then we pass in bulk to Python. The drawbacks to this method are
442
-- two fold: we cannot control the number of generated opcodes, since
443
-- HUnit assertions don't get access to the test options, and for the
444
-- same reason we can't run a repeatable seed. We should probably find
445
-- a better way to do this, for example by having a
446
-- separately-launched Python process (if not running the tests would
447
-- be skipped).
448
case_py_compat_types :: HUnit.Assertion
449
case_py_compat_types = do
450
  let num_opcodes = length OpCodes.allOpIDs * 100
451
  opcodes <- genSample (vectorOf num_opcodes
452
                                   (arbitrary::Gen OpCodes.MetaOpCode))
453
  let with_sum = map (\o -> (OpCodes.opSummary $
454
                             OpCodes.metaOpCode o, o)) opcodes
455
      serialized = J.encode opcodes
456
  -- check for non-ASCII fields, usually due to 'arbitrary :: String'
457
  mapM_ (\op -> when (any (not . isAscii) (J.encode op)) .
458
                HUnit.assertFailure $
459
                  "OpCode has non-ASCII fields: " ++ show op
460
        ) opcodes
461
  py_stdout <-
462
     runPython "from ganeti import opcodes\n\
463
               \import sys\n\
464
               \from ganeti import serializer\n\
465
               \op_data = serializer.Load(sys.stdin.read())\n\
466
               \decoded = [opcodes.OpCode.LoadOpCode(o) for o in op_data]\n\
467
               \for op in decoded:\n\
468
               \  op.Validate(True)\n\
469
               \encoded = [(op.Summary(), op.__getstate__())\n\
470
               \           for op in decoded]\n\
471
               \print serializer.Dump(encoded)" serialized
472
     >>= checkPythonResult
473
  let deserialised =
474
        J.decode py_stdout::J.Result [(String, OpCodes.MetaOpCode)]
475
  decoded <- case deserialised of
476
               J.Ok ops -> return ops
477
               J.Error msg ->
478
                 HUnit.assertFailure ("Unable to decode opcodes: " ++ msg)
479
                 -- this already raised an expection, but we need it
480
                 -- for proper types
481
                 >> fail "Unable to decode opcodes"
482
  HUnit.assertEqual "Mismatch in number of returned opcodes"
483
    (length decoded) (length with_sum)
484
  mapM_ (uncurry (HUnit.assertEqual "Different result after encoding/decoding")
485
        ) $ zip decoded with_sum
486

    
487
-- | Custom HUnit test case that forks a Python process and checks
488
-- correspondence between Haskell OpCodes fields and their Python
489
-- equivalent.
490
case_py_compat_fields :: HUnit.Assertion
491
case_py_compat_fields = do
492
  let hs_fields = sort $ map (\op_id -> (op_id, OpCodes.allOpFields op_id))
493
                         OpCodes.allOpIDs
494
  py_stdout <-
495
     runPython "from ganeti import opcodes\n\
496
               \import sys\n\
497
               \from ganeti import serializer\n\
498
               \fields = [(k, sorted([p[0] for p in v.OP_PARAMS]))\n\
499
               \           for k, v in opcodes.OP_MAPPING.items()]\n\
500
               \print serializer.Dump(fields)" ""
501
     >>= checkPythonResult
502
  let deserialised = J.decode py_stdout::J.Result [(String, [String])]
503
  py_fields <- case deserialised of
504
                 J.Ok v -> return $ sort v
505
                 J.Error msg ->
506
                   HUnit.assertFailure ("Unable to decode op fields: " ++ msg)
507
                   -- this already raised an expection, but we need it
508
                   -- for proper types
509
                   >> fail "Unable to decode op fields"
510
  HUnit.assertEqual "Mismatch in number of returned opcodes"
511
    (length hs_fields) (length py_fields)
512
  HUnit.assertEqual "Mismatch in defined OP_IDs"
513
    (map fst hs_fields) (map fst py_fields)
514
  mapM_ (\((py_id, py_flds), (hs_id, hs_flds)) -> do
515
           HUnit.assertEqual "Mismatch in OP_ID" py_id hs_id
516
           HUnit.assertEqual ("Mismatch in fields for " ++ hs_id)
517
             py_flds hs_flds
518
        ) $ zip py_fields hs_fields
519

    
520
-- | Checks that setOpComment works correctly.
521
prop_setOpComment :: OpCodes.MetaOpCode -> String -> Property
522
prop_setOpComment op comment =
523
  let (OpCodes.MetaOpCode common _) = OpCodes.setOpComment comment op
524
  in OpCodes.opComment common ==? Just comment
525

    
526
-- | Tests wrong tag object building (cluster takes only jsnull, the
527
-- other take a string, so we test the opposites).
528
case_TagObject_fail :: Assertion
529
case_TagObject_fail =
530
  mapM_ (\(t, j) -> assertEqual (show t ++ "/" ++ J.encode j) Nothing $
531
                    tagObjectFrom t j)
532
    [ (TagTypeCluster,  J.showJSON "abc")
533
    , (TagTypeInstance, J.JSNull)
534
    , (TagTypeNode,     J.JSNull)
535
    , (TagTypeGroup,    J.JSNull)
536
    ]
537

    
538
-- | Tests wrong (negative) disk index.
539
prop_mkDiskIndex_fail :: QuickCheck.Positive Int -> Property
540
prop_mkDiskIndex_fail (Positive i) =
541
  case mkDiskIndex (negate i) of
542
    Bad msg -> printTestCase "error message " $
543
               "Invalid value" `isPrefixOf` msg
544
    Ok v -> failTest $ "Succeeded to build disk index '" ++ show v ++
545
                       "' from negative value " ++ show (negate i)
546

    
547
-- | Tests a few invalid 'readRecreateDisks' cases.
548
case_readRecreateDisks_fail :: Assertion
549
case_readRecreateDisks_fail = do
550
  assertBool "null" $
551
    isJsonError (J.readJSON J.JSNull::J.Result RecreateDisksInfo)
552
  assertBool "string" $
553
    isJsonError (J.readJSON (J.showJSON "abc")::J.Result RecreateDisksInfo)
554

    
555
-- | Tests a few invalid 'readDdmOldChanges' cases.
556
case_readDdmOldChanges_fail :: Assertion
557
case_readDdmOldChanges_fail = do
558
  assertBool "null" $
559
    isJsonError (J.readJSON J.JSNull::J.Result DdmOldChanges)
560
  assertBool "string" $
561
    isJsonError (J.readJSON (J.showJSON "abc")::J.Result DdmOldChanges)
562

    
563
-- | Tests a few invalid 'readExportTarget' cases.
564
case_readExportTarget_fail :: Assertion
565
case_readExportTarget_fail = do
566
  assertBool "null" $
567
    isJsonError (J.readJSON J.JSNull::J.Result ExportTarget)
568
  assertBool "int" $
569
    isJsonError (J.readJSON (J.showJSON (5::Int))::J.Result ExportTarget)
570

    
571
testSuite "OpCodes"
572
            [ 'prop_serialization
573
            , 'case_AllDefined
574
            , 'case_py_compat_types
575
            , 'case_py_compat_fields
576
            , 'prop_setOpComment
577
            , 'case_TagObject_fail
578
            , 'prop_mkDiskIndex_fail
579
            , 'case_readRecreateDisks_fail
580
            , 'case_readDdmOldChanges_fail
581
            , 'case_readExportTarget_fail
582
            ]