Statistics
| Branch: | Tag: | Revision:

root / test / hs / Test / Ganeti / OpCodes.hs @ 7d81bb8b

History | View | Annotate | Download (26.2 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 (Ord k, Arbitrary k, Arbitrary a) => Arbitrary (Map.Map k a) where
62
  arbitrary = Map.fromList <$> arbitrary
63

    
64
arbitraryOpTagsGet :: Gen OpCodes.OpCode
65
arbitraryOpTagsGet = do
66
  kind <- arbitrary
67
  OpCodes.OpTagsSet kind <$> arbitrary <*> genOpCodesTagName kind
68

    
69
arbitraryOpTagsSet :: Gen OpCodes.OpCode
70
arbitraryOpTagsSet = do
71
  kind <- arbitrary
72
  OpCodes.OpTagsSet kind <$> genTags <*> genOpCodesTagName kind
73

    
74
arbitraryOpTagsDel :: Gen OpCodes.OpCode
75
arbitraryOpTagsDel = do
76
  kind <- arbitrary
77
  OpCodes.OpTagsDel kind <$> genTags <*> genOpCodesTagName kind
78

    
79
$(genArbitrary ''OpCodes.ReplaceDisksMode)
80

    
81
$(genArbitrary ''DiskAccess)
82

    
83
instance Arbitrary OpCodes.DiskIndex where
84
  arbitrary = choose (0, C.maxDisks - 1) >>= OpCodes.mkDiskIndex
85

    
86
instance Arbitrary INicParams where
87
  arbitrary = INicParams <$> genMaybe genNameNE <*> genMaybe genName <*>
88
              genMaybe genNameNE <*> genMaybe genNameNE <*>
89
              genMaybe genNameNE <*> genMaybe genNameNE <*>
90
              genMaybe genNameNE
91

    
92
instance Arbitrary IDiskParams where
93
  arbitrary = IDiskParams <$> arbitrary <*> arbitrary <*>
94
              genMaybe genNameNE <*> genMaybe genNameNE <*>
95
              genMaybe genNameNE <*> genMaybe genNameNE <*>
96
              genMaybe genNameNE <*> genMaybe genNameNE <*> genAndRestArguments
97

    
98
instance Arbitrary RecreateDisksInfo where
99
  arbitrary = oneof [ pure RecreateDisksAll
100
                    , RecreateDisksIndices <$> arbitrary
101
                    , RecreateDisksParams <$> arbitrary
102
                    ]
103

    
104
instance Arbitrary DdmOldChanges where
105
  arbitrary = oneof [ DdmOldIndex <$> arbitrary
106
                    , DdmOldMod   <$> arbitrary
107
                    ]
108

    
109
instance (Arbitrary a) => Arbitrary (SetParamsMods a) where
110
  arbitrary = oneof [ pure SetParamsEmpty
111
                    , SetParamsDeprecated <$> arbitrary
112
                    , SetParamsNew        <$> arbitrary
113
                    ]
114

    
115
instance Arbitrary ISnapParams where
116
  arbitrary = ISnapParams <$> genNameNE
117

    
118
instance (Arbitrary a) => Arbitrary (SetSnapParams a) where
119
  arbitrary = oneof [ pure SetSnapParamsEmpty
120
                    , SetSnapParamsValid <$> arbitrary
121
                    ]
122

    
123
instance Arbitrary ExportTarget where
124
  arbitrary = oneof [ ExportTargetLocal <$> genNodeNameNE
125
                    , ExportTargetRemote <$> pure []
126
                    ]
127

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

    
385
-- | Generates one element of a reason trail
386
genReasonElem :: Gen ReasonElem
387
genReasonElem = (,,) <$> genFQDN <*> genFQDN <*> arbitrary
388

    
389
-- | Generates a reason trail
390
genReasonTrail :: Gen ReasonTrail
391
genReasonTrail = do
392
  size <- choose (0, 10)
393
  vectorOf size genReasonElem
394

    
395
instance Arbitrary OpCodes.CommonOpParams where
396
  arbitrary = OpCodes.CommonOpParams <$> arbitrary <*> arbitrary <*>
397
                arbitrary <*> resize 5 arbitrary <*> genMaybe genName <*>
398
                genReasonTrail
399

    
400
-- * Helper functions
401

    
402
-- | Empty JSObject.
403
emptyJSObject :: J.JSObject J.JSValue
404
emptyJSObject = J.toJSObject []
405

    
406
-- | Empty maybe unchecked dictionary.
407
emptyMUD :: Gen (Maybe (J.JSObject J.JSValue))
408
emptyMUD = genMaybe $ pure emptyJSObject
409

    
410
-- | Generates an empty container.
411
genEmptyContainer :: (Ord a) => Gen (GenericContainer a b)
412
genEmptyContainer = pure . GenericContainer $ Map.fromList []
413

    
414
-- | Generates list of disk indices.
415
genDiskIndices :: Gen [DiskIndex]
416
genDiskIndices = do
417
  cnt <- choose (0, C.maxDisks)
418
  genUniquesList cnt arbitrary
419

    
420
-- | Generates a list of node names.
421
genNodeNames :: Gen [String]
422
genNodeNames = resize maxNodes (listOf genFQDN)
423

    
424
-- | Generates a list of node names in non-empty string type.
425
genNodeNamesNE :: Gen [NonEmptyString]
426
genNodeNamesNE = genNodeNames >>= mapM mkNonEmpty
427

    
428
-- | Gets a node name in non-empty type.
429
genNodeNameNE :: Gen NonEmptyString
430
genNodeNameNE = genFQDN >>= mkNonEmpty
431

    
432
-- | Gets a name (non-fqdn) in non-empty type.
433
genNameNE :: Gen NonEmptyString
434
genNameNE = genName >>= mkNonEmpty
435

    
436
-- | Gets a list of names (non-fqdn) in non-empty type.
437
genNamesNE :: Gen [NonEmptyString]
438
genNamesNE = resize maxNodes (listOf genNameNE)
439

    
440
-- | Returns a list of non-empty fields.
441
genFieldsNE :: Gen [NonEmptyString]
442
genFieldsNE = genFields >>= mapM mkNonEmpty
443

    
444
-- | Generate a 3-byte MAC prefix.
445
genMacPrefix :: Gen NonEmptyString
446
genMacPrefix = do
447
  octets <- vectorOf 3 $ choose (0::Int, 255)
448
  mkNonEmpty . intercalate ":" $ map (printf "%02x") octets
449

    
450
-- | Arbitrary instance for MetaOpCode, defined here due to TH ordering.
451
$(genArbitrary ''OpCodes.MetaOpCode)
452

    
453
-- | Small helper to check for a failed JSON deserialisation
454
isJsonError :: J.Result a -> Bool
455
isJsonError (J.Error _) = True
456
isJsonError _           = False
457

    
458
-- * Test cases
459

    
460
-- | Check that opcode serialization is idempotent.
461
prop_serialization :: OpCodes.OpCode -> Property
462
prop_serialization = testSerialisation
463

    
464
-- | Check that Python and Haskell defined the same opcode list.
465
case_AllDefined :: HUnit.Assertion
466
case_AllDefined = do
467
  py_stdout <-
468
     runPython "from ganeti import opcodes\n\
469
               \from ganeti import serializer\n\
470
               \import sys\n\
471
               \print serializer.Dump([opid for opid in opcodes.OP_MAPPING])\n"
472
               ""
473
     >>= checkPythonResult
474
  py_ops <- case J.decode py_stdout::J.Result [String] of
475
               J.Ok ops -> return ops
476
               J.Error msg ->
477
                 HUnit.assertFailure ("Unable to decode opcode names: " ++ msg)
478
                 -- this already raised an expection, but we need it
479
                 -- for proper types
480
                 >> fail "Unable to decode opcode names"
481
  let hs_ops = sort OpCodes.allOpIDs
482
      extra_py = py_ops \\ hs_ops
483
      extra_hs = hs_ops \\ py_ops
484
  HUnit.assertBool ("Missing OpCodes from the Haskell code:\n" ++
485
                    unlines extra_py) (null extra_py)
486
  HUnit.assertBool ("Extra OpCodes in the Haskell code code:\n" ++
487
                    unlines extra_hs) (null extra_hs)
488

    
489
-- | Custom HUnit test case that forks a Python process and checks
490
-- correspondence between Haskell-generated OpCodes and their Python
491
-- decoded, validated and re-encoded version.
492
--
493
-- Note that we have a strange beast here: since launching Python is
494
-- expensive, we don't do this via a usual QuickProperty, since that's
495
-- slow (I've tested it, and it's indeed quite slow). Rather, we use a
496
-- single HUnit assertion, and in it we manually use QuickCheck to
497
-- generate 500 opcodes times the number of defined opcodes, which
498
-- then we pass in bulk to Python. The drawbacks to this method are
499
-- two fold: we cannot control the number of generated opcodes, since
500
-- HUnit assertions don't get access to the test options, and for the
501
-- same reason we can't run a repeatable seed. We should probably find
502
-- a better way to do this, for example by having a
503
-- separately-launched Python process (if not running the tests would
504
-- be skipped).
505
case_py_compat_types :: HUnit.Assertion
506
case_py_compat_types = do
507
  let num_opcodes = length OpCodes.allOpIDs * 100
508
  opcodes <- genSample (vectorOf num_opcodes
509
                                   (arbitrary::Gen OpCodes.MetaOpCode))
510
  let with_sum = map (\o -> (OpCodes.opSummary $
511
                             OpCodes.metaOpCode o, o)) opcodes
512
      serialized = J.encode opcodes
513
  -- check for non-ASCII fields, usually due to 'arbitrary :: String'
514
  mapM_ (\op -> when (any (not . isAscii) (J.encode op)) .
515
                HUnit.assertFailure $
516
                  "OpCode has non-ASCII fields: " ++ show op
517
        ) opcodes
518
  py_stdout <-
519
     runPython "from ganeti import opcodes\n\
520
               \from ganeti import serializer\n\
521
               \import sys\n\
522
               \op_data = serializer.Load(sys.stdin.read())\n\
523
               \decoded = [opcodes.OpCode.LoadOpCode(o) for o in op_data]\n\
524
               \for op in decoded:\n\
525
               \  op.Validate(True)\n\
526
               \encoded = [(op.Summary(), op.__getstate__())\n\
527
               \           for op in decoded]\n\
528
               \print serializer.Dump(encoded)" serialized
529
     >>= checkPythonResult
530
  let deserialised =
531
        J.decode py_stdout::J.Result [(String, OpCodes.MetaOpCode)]
532
  decoded <- case deserialised of
533
               J.Ok ops -> return ops
534
               J.Error msg ->
535
                 HUnit.assertFailure ("Unable to decode opcodes: " ++ msg)
536
                 -- this already raised an expection, but we need it
537
                 -- for proper types
538
                 >> fail "Unable to decode opcodes"
539
  HUnit.assertEqual "Mismatch in number of returned opcodes"
540
    (length decoded) (length with_sum)
541
  mapM_ (uncurry (HUnit.assertEqual "Different result after encoding/decoding")
542
        ) $ zip decoded with_sum
543

    
544
-- | Custom HUnit test case that forks a Python process and checks
545
-- correspondence between Haskell OpCodes fields and their Python
546
-- equivalent.
547
case_py_compat_fields :: HUnit.Assertion
548
case_py_compat_fields = do
549
  let hs_fields = sort $ map (\op_id -> (op_id, OpCodes.allOpFields op_id))
550
                         OpCodes.allOpIDs
551
  py_stdout <-
552
     runPython "from ganeti import opcodes\n\
553
               \import sys\n\
554
               \from ganeti import serializer\n\
555
               \fields = [(k, sorted([p[0] for p in v.OP_PARAMS]))\n\
556
               \           for k, v in opcodes.OP_MAPPING.items()]\n\
557
               \print serializer.Dump(fields)" ""
558
     >>= checkPythonResult
559
  let deserialised = J.decode py_stdout::J.Result [(String, [String])]
560
  py_fields <- case deserialised of
561
                 J.Ok v -> return $ sort v
562
                 J.Error msg ->
563
                   HUnit.assertFailure ("Unable to decode op fields: " ++ msg)
564
                   -- this already raised an expection, but we need it
565
                   -- for proper types
566
                   >> fail "Unable to decode op fields"
567
  HUnit.assertEqual "Mismatch in number of returned opcodes"
568
    (length hs_fields) (length py_fields)
569
  HUnit.assertEqual "Mismatch in defined OP_IDs"
570
    (map fst hs_fields) (map fst py_fields)
571
  mapM_ (\((py_id, py_flds), (hs_id, hs_flds)) -> do
572
           HUnit.assertEqual "Mismatch in OP_ID" py_id hs_id
573
           HUnit.assertEqual ("Mismatch in fields for " ++ hs_id)
574
             py_flds hs_flds
575
        ) $ zip py_fields hs_fields
576

    
577
-- | Checks that setOpComment works correctly.
578
prop_setOpComment :: OpCodes.MetaOpCode -> String -> Property
579
prop_setOpComment op comment =
580
  let (OpCodes.MetaOpCode common _) = OpCodes.setOpComment comment op
581
  in OpCodes.opComment common ==? Just comment
582

    
583
-- | Tests wrong (negative) disk index.
584
prop_mkDiskIndex_fail :: QuickCheck.Positive Int -> Property
585
prop_mkDiskIndex_fail (Positive i) =
586
  case mkDiskIndex (negate i) of
587
    Bad msg -> printTestCase "error message " $
588
               "Invalid value" `isPrefixOf` msg
589
    Ok v -> failTest $ "Succeeded to build disk index '" ++ show v ++
590
                       "' from negative value " ++ show (negate i)
591

    
592
-- | Tests a few invalid 'readRecreateDisks' cases.
593
case_readRecreateDisks_fail :: Assertion
594
case_readRecreateDisks_fail = do
595
  assertBool "null" $
596
    isJsonError (J.readJSON J.JSNull::J.Result RecreateDisksInfo)
597
  assertBool "string" $
598
    isJsonError (J.readJSON (J.showJSON "abc")::J.Result RecreateDisksInfo)
599

    
600
-- | Tests a few invalid 'readDdmOldChanges' cases.
601
case_readDdmOldChanges_fail :: Assertion
602
case_readDdmOldChanges_fail = do
603
  assertBool "null" $
604
    isJsonError (J.readJSON J.JSNull::J.Result DdmOldChanges)
605
  assertBool "string" $
606
    isJsonError (J.readJSON (J.showJSON "abc")::J.Result DdmOldChanges)
607

    
608
-- | Tests a few invalid 'readExportTarget' cases.
609
case_readExportTarget_fail :: Assertion
610
case_readExportTarget_fail = do
611
  assertBool "null" $
612
    isJsonError (J.readJSON J.JSNull::J.Result ExportTarget)
613
  assertBool "int" $
614
    isJsonError (J.readJSON (J.showJSON (5::Int))::J.Result ExportTarget)
615

    
616
testSuite "OpCodes"
617
            [ 'prop_serialization
618
            , 'case_AllDefined
619
            , 'case_py_compat_types
620
            , 'case_py_compat_fields
621
            , 'prop_setOpComment
622
            , 'prop_mkDiskIndex_fail
623
            , 'case_readRecreateDisks_fail
624
            , 'case_readDdmOldChanges_fail
625
            , 'case_readExportTarget_fail
626
            ]