Statistics
| Branch: | Tag: | Revision:

root / test / hs / Test / Ganeti / OpCodes.hs @ aa7a5c90

History | View | Annotate | Download (24.3 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
instance Arbitrary OpCodes.DiskIndex where
73
  arbitrary = choose (0, C.maxDisks - 1) >>= OpCodes.mkDiskIndex
74

    
75
instance Arbitrary INicParams where
76
  arbitrary = INicParams <$> genMaybe genNameNE <*> genMaybe genName <*>
77
              genMaybe genNameNE <*> genMaybe genNameNE <*> genMaybe genNameNE
78

    
79
instance Arbitrary IDiskParams where
80
  arbitrary = IDiskParams <$> arbitrary <*> arbitrary <*>
81
              genMaybe genNameNE <*> genMaybe genNameNE <*>
82
              genMaybe genNameNE <*> genMaybe genNameNE
83

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

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

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

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

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

    
346
-- | Generates one element of a reason trail
347
genReasonElem :: Gen ReasonElem
348
genReasonElem = (,,) <$> genFQDN <*> genFQDN <*> arbitrary
349

    
350
-- | Generates a reason trail
351
genReasonTrail :: Gen ReasonTrail
352
genReasonTrail = do
353
  size <- choose (0, 10)
354
  vectorOf size genReasonElem
355

    
356
instance Arbitrary OpCodes.CommonOpParams where
357
  arbitrary = OpCodes.CommonOpParams <$> arbitrary <*> arbitrary <*>
358
                arbitrary <*> resize 5 arbitrary <*> genMaybe genName <*>
359
                genReasonTrail
360

    
361
-- * Helper functions
362

    
363
-- | Empty JSObject.
364
emptyJSObject :: J.JSObject J.JSValue
365
emptyJSObject = J.toJSObject []
366

    
367
-- | Empty maybe unchecked dictionary.
368
emptyMUD :: Gen (Maybe (J.JSObject J.JSValue))
369
emptyMUD = genMaybe $ pure emptyJSObject
370

    
371
-- | Generates an empty container.
372
genEmptyContainer :: (Ord a) => Gen (GenericContainer a b)
373
genEmptyContainer = pure . GenericContainer $ Map.fromList []
374

    
375
-- | Generates list of disk indices.
376
genDiskIndices :: Gen [DiskIndex]
377
genDiskIndices = do
378
  cnt <- choose (0, C.maxDisks)
379
  genUniquesList cnt arbitrary
380

    
381
-- | Generates a list of node names.
382
genNodeNames :: Gen [String]
383
genNodeNames = resize maxNodes (listOf genFQDN)
384

    
385
-- | Generates a list of node names in non-empty string type.
386
genNodeNamesNE :: Gen [NonEmptyString]
387
genNodeNamesNE = genNodeNames >>= mapM mkNonEmpty
388

    
389
-- | Gets a node name in non-empty type.
390
genNodeNameNE :: Gen NonEmptyString
391
genNodeNameNE = genFQDN >>= mkNonEmpty
392

    
393
-- | Gets a name (non-fqdn) in non-empty type.
394
genNameNE :: Gen NonEmptyString
395
genNameNE = genName >>= mkNonEmpty
396

    
397
-- | Gets a list of names (non-fqdn) in non-empty type.
398
genNamesNE :: Gen [NonEmptyString]
399
genNamesNE = resize maxNodes (listOf genNameNE)
400

    
401
-- | Returns a list of non-empty fields.
402
genFieldsNE :: Gen [NonEmptyString]
403
genFieldsNE = genFields >>= mapM mkNonEmpty
404

    
405
-- | Generate a 3-byte MAC prefix.
406
genMacPrefix :: Gen NonEmptyString
407
genMacPrefix = do
408
  octets <- vectorOf 3 $ choose (0::Int, 255)
409
  mkNonEmpty . intercalate ":" $ map (printf "%02x") octets
410

    
411
-- | Arbitrary instance for MetaOpCode, defined here due to TH ordering.
412
$(genArbitrary ''OpCodes.MetaOpCode)
413

    
414
-- | Small helper to check for a failed JSON deserialisation
415
isJsonError :: J.Result a -> Bool
416
isJsonError (J.Error _) = True
417
isJsonError _           = False
418

    
419
-- * Test cases
420

    
421
-- | Check that opcode serialization is idempotent.
422
prop_serialization :: OpCodes.OpCode -> Property
423
prop_serialization = testSerialisation
424

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

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

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

    
525
-- | Checks that setOpComment works correctly.
526
prop_setOpComment :: OpCodes.MetaOpCode -> String -> Property
527
prop_setOpComment op comment =
528
  let (OpCodes.MetaOpCode common _) = OpCodes.setOpComment comment op
529
  in OpCodes.opComment common ==? Just comment
530

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

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

    
552
-- | Tests a few invalid 'readRecreateDisks' cases.
553
case_readRecreateDisks_fail :: Assertion
554
case_readRecreateDisks_fail = do
555
  assertBool "null" $
556
    isJsonError (J.readJSON J.JSNull::J.Result RecreateDisksInfo)
557
  assertBool "string" $
558
    isJsonError (J.readJSON (J.showJSON "abc")::J.Result RecreateDisksInfo)
559

    
560
-- | Tests a few invalid 'readDdmOldChanges' cases.
561
case_readDdmOldChanges_fail :: Assertion
562
case_readDdmOldChanges_fail = do
563
  assertBool "null" $
564
    isJsonError (J.readJSON J.JSNull::J.Result DdmOldChanges)
565
  assertBool "string" $
566
    isJsonError (J.readJSON (J.showJSON "abc")::J.Result DdmOldChanges)
567

    
568
-- | Tests a few invalid 'readExportTarget' cases.
569
case_readExportTarget_fail :: Assertion
570
case_readExportTarget_fail = do
571
  assertBool "null" $
572
    isJsonError (J.readJSON J.JSNull::J.Result ExportTarget)
573
  assertBool "int" $
574
    isJsonError (J.readJSON (J.showJSON (5::Int))::J.Result ExportTarget)
575

    
576
testSuite "OpCodes"
577
            [ 'prop_serialization
578
            , 'case_AllDefined
579
            , 'case_py_compat_types
580
            , 'case_py_compat_fields
581
            , 'prop_setOpComment
582
            , 'case_TagObject_fail
583
            , 'prop_mkDiskIndex_fail
584
            , 'case_readRecreateDisks_fail
585
            , 'case_readDdmOldChanges_fail
586
            , 'case_readExportTarget_fail
587
            ]