Statistics
| Branch: | Tag: | Revision:

root / test / hs / Test / Ganeti / OpCodes.hs @ 3039e2dc

History | View | Annotate | Download (25.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 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 <*> return Nothing <*> arbitrary
113
      "OP_INSTANCE_REPLACE_DISKS" ->
114
        OpCodes.OpInstanceReplaceDisks <$> genFQDN <*> return Nothing <*>
115
          arbitrary <*> arbitrary <*> arbitrary <*> genDiskIndices <*>
116
          genMaybe genNodeNameNE <*> return Nothing <*> genMaybe genNameNE
117
      "OP_INSTANCE_FAILOVER" ->
118
        OpCodes.OpInstanceFailover <$> genFQDN <*> return Nothing <*>
119
        arbitrary <*> arbitrary <*> genMaybe genNodeNameNE <*>
120
        return Nothing <*> arbitrary <*> genMaybe genNameNE
121
      "OP_INSTANCE_MIGRATE" ->
122
        OpCodes.OpInstanceMigrate <$> genFQDN <*> return Nothing <*>
123
          arbitrary <*> arbitrary <*> genMaybe genNodeNameNE <*>
124
          return Nothing <*> arbitrary <*> arbitrary <*> arbitrary <*>
125
          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 <$> arbitrary <*> 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 <*> arbitrary <*>
167
          genMaybe (genName >>= mkNonEmpty)
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 <*> return Nothing <*>
179
          arbitrary <*> arbitrary <*> arbitrary <*>
180
          (arbitrary `suchThat` (>0))
181
      "OP_NODE_REMOVE" ->
182
        OpCodes.OpNodeRemove <$> genNodeNameNE <*> return Nothing
183
      "OP_NODE_ADD" ->
184
        OpCodes.OpNodeAdd <$> genNodeNameNE <*> emptyMUD <*> emptyMUD <*>
185
          genMaybe genName <*> genMaybe genNameNE <*> arbitrary <*>
186
          genMaybe genNameNE <*> arbitrary <*> arbitrary <*> emptyMUD
187
      "OP_NODE_QUERY" ->
188
        OpCodes.OpNodeQuery <$> genFieldsNE <*> genNamesNE <*> arbitrary
189
      "OP_NODE_QUERYVOLS" ->
190
        OpCodes.OpNodeQueryvols <$> arbitrary <*> genNodeNamesNE
191
      "OP_NODE_QUERY_STORAGE" ->
192
        OpCodes.OpNodeQueryStorage <$> arbitrary <*> arbitrary <*>
193
          genNodeNamesNE <*> genNameNE
194
      "OP_NODE_MODIFY_STORAGE" ->
195
        OpCodes.OpNodeModifyStorage <$> genNodeNameNE <*> return Nothing <*>
196
          arbitrary <*> genNameNE <*> pure emptyJSObject
197
      "OP_REPAIR_NODE_STORAGE" ->
198
        OpCodes.OpRepairNodeStorage <$> genNodeNameNE <*> return Nothing <*>
199
          arbitrary <*> genNameNE <*> arbitrary
200
      "OP_NODE_SET_PARAMS" ->
201
        OpCodes.OpNodeSetParams <$> genNodeNameNE <*> return Nothing <*>
202
          arbitrary <*> emptyMUD <*> emptyMUD <*> arbitrary <*> arbitrary <*>
203
          arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*>
204
          genMaybe genNameNE <*> emptyMUD <*> arbitrary
205
      "OP_NODE_POWERCYCLE" ->
206
        OpCodes.OpNodePowercycle <$> genNodeNameNE <*> return Nothing <*>
207
          arbitrary
208
      "OP_NODE_MIGRATE" ->
209
        OpCodes.OpNodeMigrate <$> genNodeNameNE <*> return Nothing <*>
210
          arbitrary <*> arbitrary <*> genMaybe genNodeNameNE <*>
211
          return Nothing <*> arbitrary <*> arbitrary <*> genMaybe genNameNE
212
      "OP_NODE_EVACUATE" ->
213
        OpCodes.OpNodeEvacuate <$> arbitrary <*> genNodeNameNE <*>
214
          return Nothing <*> genMaybe genNodeNameNE <*> return Nothing <*>
215
          genMaybe genNameNE <*> arbitrary
216
      "OP_INSTANCE_CREATE" ->
217
        OpCodes.OpInstanceCreate <$> genFQDN <*> arbitrary <*>
218
          arbitrary <*> arbitrary <*> arbitrary <*> pure emptyJSObject <*>
219
          arbitrary <*> arbitrary <*> arbitrary <*> genMaybe genNameNE <*>
220
          pure emptyJSObject <*> arbitrary <*> genMaybe genNameNE <*>
221
          arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*>
222
          arbitrary <*> arbitrary <*> pure emptyJSObject <*>
223
          genMaybe genNameNE <*>
224
          genMaybe genNodeNameNE <*> return Nothing <*>
225
          genMaybe genNodeNameNE <*> return Nothing <*>
226
          genMaybe (pure []) <*> genMaybe genNodeNameNE <*>
227
          arbitrary <*> genMaybe genNodeNameNE <*> return Nothing <*>
228
          genMaybe genNodeNameNE <*> genMaybe genNameNE <*>
229
          arbitrary <*> arbitrary <*> (genTags >>= mapM mkNonEmpty)
230
      "OP_INSTANCE_MULTI_ALLOC" ->
231
        OpCodes.OpInstanceMultiAlloc <$> genMaybe genNameNE <*> pure [] <*>
232
          arbitrary
233
      "OP_INSTANCE_REINSTALL" ->
234
        OpCodes.OpInstanceReinstall <$> genFQDN <*> return Nothing <*>
235
          arbitrary <*> genMaybe genNameNE <*> genMaybe (pure emptyJSObject)
236
      "OP_INSTANCE_REMOVE" ->
237
        OpCodes.OpInstanceRemove <$> genFQDN <*> return Nothing <*>
238
          arbitrary <*> arbitrary
239
      "OP_INSTANCE_RENAME" ->
240
        OpCodes.OpInstanceRename <$> genFQDN <*> return Nothing <*>
241
          genNodeNameNE <*> arbitrary <*> arbitrary
242
      "OP_INSTANCE_STARTUP" ->
243
        OpCodes.OpInstanceStartup <$> genFQDN <*> return Nothing <*>
244
          arbitrary <*> arbitrary <*> pure emptyJSObject <*>
245
          pure emptyJSObject <*> arbitrary <*> arbitrary
246
      "OP_INSTANCE_SHUTDOWN" ->
247
        OpCodes.OpInstanceShutdown <$> genFQDN <*> return Nothing <*>
248
          arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
249
      "OP_INSTANCE_REBOOT" ->
250
        OpCodes.OpInstanceReboot <$> genFQDN <*> return Nothing <*>
251
          arbitrary <*> arbitrary <*> arbitrary
252
      "OP_INSTANCE_MOVE" ->
253
        OpCodes.OpInstanceMove <$> genFQDN <*> return Nothing <*>
254
          arbitrary <*> arbitrary <*> genNodeNameNE <*> return Nothing <*>
255
          arbitrary
256
      "OP_INSTANCE_CONSOLE" -> OpCodes.OpInstanceConsole <$> genFQDN <*>
257
          return Nothing
258
      "OP_INSTANCE_ACTIVATE_DISKS" ->
259
        OpCodes.OpInstanceActivateDisks <$> genFQDN <*> return Nothing <*>
260
          arbitrary <*> arbitrary
261
      "OP_INSTANCE_DEACTIVATE_DISKS" ->
262
        OpCodes.OpInstanceDeactivateDisks <$> genFQDN <*> return Nothing <*>
263
          arbitrary
264
      "OP_INSTANCE_RECREATE_DISKS" ->
265
        OpCodes.OpInstanceRecreateDisks <$> genFQDN <*> return Nothing <*>
266
          arbitrary <*> genNodeNamesNE <*> return Nothing <*>
267
          genMaybe genNameNE
268
      "OP_INSTANCE_QUERY" ->
269
        OpCodes.OpInstanceQuery <$> genFieldsNE <*> genNamesNE <*> arbitrary
270
      "OP_INSTANCE_QUERY_DATA" ->
271
        OpCodes.OpInstanceQueryData <$> arbitrary <*>
272
          genNodeNamesNE <*> arbitrary
273
      "OP_INSTANCE_SET_PARAMS" ->
274
        OpCodes.OpInstanceSetParams <$> genFQDN <*> return Nothing <*>
275
          arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*>
276
          arbitrary <*> pure emptyJSObject <*> arbitrary <*>
277
          pure emptyJSObject <*> arbitrary <*> genMaybe genNodeNameNE <*>
278
          return Nothing <*> genMaybe genNodeNameNE <*> return Nothing <*>
279
          genMaybe genNameNE <*> pure emptyJSObject <*> arbitrary <*>
280
          arbitrary <*> arbitrary
281
      "OP_INSTANCE_GROW_DISK" ->
282
        OpCodes.OpInstanceGrowDisk <$> genFQDN <*> return Nothing <*>
283
          arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
284
      "OP_INSTANCE_CHANGE_GROUP" ->
285
        OpCodes.OpInstanceChangeGroup <$> genFQDN <*> return Nothing <*>
286
          arbitrary <*> genMaybe genNameNE <*>
287
          genMaybe (resize maxNodes (listOf genNameNE))
288
      "OP_GROUP_ADD" ->
289
        OpCodes.OpGroupAdd <$> genNameNE <*> arbitrary <*>
290
          emptyMUD <*> genMaybe genEmptyContainer <*>
291
          emptyMUD <*> emptyMUD <*> emptyMUD
292
      "OP_GROUP_ASSIGN_NODES" ->
293
        OpCodes.OpGroupAssignNodes <$> genNameNE <*> arbitrary <*>
294
          genNodeNamesNE <*> return Nothing
295
      "OP_GROUP_QUERY" ->
296
        OpCodes.OpGroupQuery <$> genFieldsNE <*> genNamesNE
297
      "OP_GROUP_SET_PARAMS" ->
298
        OpCodes.OpGroupSetParams <$> genNameNE <*> arbitrary <*>
299
          emptyMUD <*> genMaybe genEmptyContainer <*>
300
          emptyMUD <*> emptyMUD <*> emptyMUD
301
      "OP_GROUP_REMOVE" ->
302
        OpCodes.OpGroupRemove <$> genNameNE
303
      "OP_GROUP_RENAME" ->
304
        OpCodes.OpGroupRename <$> genNameNE <*> genNameNE
305
      "OP_GROUP_EVACUATE" ->
306
        OpCodes.OpGroupEvacuate <$> genNameNE <*> arbitrary <*>
307
          genMaybe genNameNE <*> genMaybe genNamesNE
308
      "OP_OS_DIAGNOSE" ->
309
        OpCodes.OpOsDiagnose <$> genFieldsNE <*> genNamesNE
310
      "OP_EXT_STORAGE_DIAGNOSE" ->
311
        OpCodes.OpOsDiagnose <$> genFieldsNE <*> genNamesNE
312
      "OP_BACKUP_QUERY" ->
313
        OpCodes.OpBackupQuery <$> arbitrary <*> genNodeNamesNE
314
      "OP_BACKUP_PREPARE" ->
315
        OpCodes.OpBackupPrepare <$> genFQDN <*> return Nothing <*> arbitrary
316
      "OP_BACKUP_EXPORT" ->
317
        OpCodes.OpBackupExport <$> genFQDN <*> return Nothing <*>
318
          arbitrary <*> arbitrary <*> return Nothing <*> arbitrary <*>
319
          arbitrary <*> arbitrary <*> arbitrary <*> genMaybe (pure []) <*>
320
          genMaybe genNameNE
321
      "OP_BACKUP_REMOVE" ->
322
        OpCodes.OpBackupRemove <$> genFQDN <*> return Nothing
323
      "OP_TEST_ALLOCATOR" ->
324
        OpCodes.OpTestAllocator <$> arbitrary <*> arbitrary <*>
325
          genNameNE <*> pure [] <*> pure [] <*>
326
          arbitrary <*> genMaybe genNameNE <*>
327
          (genTags >>= mapM mkNonEmpty) <*>
328
          arbitrary <*> arbitrary <*> genMaybe genNameNE <*>
329
          arbitrary <*> genMaybe genNodeNamesNE <*> arbitrary <*>
330
          genMaybe genNamesNE <*> arbitrary <*> arbitrary
331
      "OP_TEST_JQUEUE" ->
332
        OpCodes.OpTestJqueue <$> arbitrary <*> arbitrary <*>
333
          resize 20 (listOf genFQDN) <*> arbitrary
334
      "OP_TEST_DUMMY" ->
335
        OpCodes.OpTestDummy <$> pure J.JSNull <*> pure J.JSNull <*>
336
          pure J.JSNull <*> pure J.JSNull
337
      "OP_NETWORK_ADD" ->
338
        OpCodes.OpNetworkAdd <$> genNameNE <*> genIp4Net <*>
339
          genMaybe genIp4Addr <*> pure Nothing <*> pure Nothing <*>
340
          genMaybe genMacPrefix <*> genMaybe (listOf genIp4Addr) <*>
341
          arbitrary <*> (genTags >>= mapM mkNonEmpty)
342
      "OP_NETWORK_REMOVE" ->
343
        OpCodes.OpNetworkRemove <$> genNameNE <*> arbitrary
344
      "OP_NETWORK_SET_PARAMS" ->
345
        OpCodes.OpNetworkSetParams <$> genNameNE <*>
346
          genMaybe genIp4Addr <*> pure Nothing <*> pure Nothing <*>
347
          genMaybe genMacPrefix <*> genMaybe (listOf genIp4Addr) <*>
348
          genMaybe (listOf genIp4Addr)
349
      "OP_NETWORK_CONNECT" ->
350
        OpCodes.OpNetworkConnect <$> genNameNE <*> genNameNE <*>
351
          arbitrary <*> genNameNE <*> arbitrary
352
      "OP_NETWORK_DISCONNECT" ->
353
        OpCodes.OpNetworkDisconnect <$> genNameNE <*> genNameNE
354
      "OP_NETWORK_QUERY" ->
355
        OpCodes.OpNetworkQuery <$> genFieldsNE <*> genNamesNE <*> arbitrary
356
      "OP_RESTRICTED_COMMAND" ->
357
        OpCodes.OpRestrictedCommand <$> arbitrary <*> genNodeNamesNE <*>
358
          return Nothing <*> genNameNE
359
      _ -> fail $ "Undefined arbitrary for opcode " ++ op_id
360

    
361
-- | Generates one element of a reason trail
362
genReasonElem :: Gen ReasonElem
363
genReasonElem = (,,) <$> genFQDN <*> genFQDN <*> arbitrary
364

    
365
-- | Generates a reason trail
366
genReasonTrail :: Gen ReasonTrail
367
genReasonTrail = do
368
  size <- choose (0, 10)
369
  vectorOf size genReasonElem
370

    
371
instance Arbitrary OpCodes.CommonOpParams where
372
  arbitrary = OpCodes.CommonOpParams <$> arbitrary <*> arbitrary <*>
373
                arbitrary <*> resize 5 arbitrary <*> genMaybe genName <*>
374
                genReasonTrail
375

    
376
-- * Helper functions
377

    
378
-- | Empty JSObject.
379
emptyJSObject :: J.JSObject J.JSValue
380
emptyJSObject = J.toJSObject []
381

    
382
-- | Empty maybe unchecked dictionary.
383
emptyMUD :: Gen (Maybe (J.JSObject J.JSValue))
384
emptyMUD = genMaybe $ pure emptyJSObject
385

    
386
-- | Generates an empty container.
387
genEmptyContainer :: (Ord a) => Gen (GenericContainer a b)
388
genEmptyContainer = pure . GenericContainer $ Map.fromList []
389

    
390
-- | Generates list of disk indices.
391
genDiskIndices :: Gen [DiskIndex]
392
genDiskIndices = do
393
  cnt <- choose (0, C.maxDisks)
394
  genUniquesList cnt arbitrary
395

    
396
-- | Generates a list of node names.
397
genNodeNames :: Gen [String]
398
genNodeNames = resize maxNodes (listOf genFQDN)
399

    
400
-- | Generates a list of node names in non-empty string type.
401
genNodeNamesNE :: Gen [NonEmptyString]
402
genNodeNamesNE = genNodeNames >>= mapM mkNonEmpty
403

    
404
-- | Gets a node name in non-empty type.
405
genNodeNameNE :: Gen NonEmptyString
406
genNodeNameNE = genFQDN >>= mkNonEmpty
407

    
408
-- | Gets a name (non-fqdn) in non-empty type.
409
genNameNE :: Gen NonEmptyString
410
genNameNE = genName >>= mkNonEmpty
411

    
412
-- | Gets a list of names (non-fqdn) in non-empty type.
413
genNamesNE :: Gen [NonEmptyString]
414
genNamesNE = resize maxNodes (listOf genNameNE)
415

    
416
-- | Returns a list of non-empty fields.
417
genFieldsNE :: Gen [NonEmptyString]
418
genFieldsNE = genFields >>= mapM mkNonEmpty
419

    
420
-- | Generate a 3-byte MAC prefix.
421
genMacPrefix :: Gen NonEmptyString
422
genMacPrefix = do
423
  octets <- vectorOf 3 $ choose (0::Int, 255)
424
  mkNonEmpty . intercalate ":" $ map (printf "%02x") octets
425

    
426
-- | Arbitrary instance for MetaOpCode, defined here due to TH ordering.
427
$(genArbitrary ''OpCodes.MetaOpCode)
428

    
429
-- | Small helper to check for a failed JSON deserialisation
430
isJsonError :: J.Result a -> Bool
431
isJsonError (J.Error _) = True
432
isJsonError _           = False
433

    
434
-- * Test cases
435

    
436
-- | Check that opcode serialization is idempotent.
437
prop_serialization :: OpCodes.OpCode -> Property
438
prop_serialization = testSerialisation
439

    
440
-- | Check that Python and Haskell defined the same opcode list.
441
case_AllDefined :: HUnit.Assertion
442
case_AllDefined = do
443
  let py_ops = sort C.opcodesOpIds
444
      hs_ops = sort OpCodes.allOpIDs
445
      extra_py = py_ops \\ hs_ops
446
      extra_hs = hs_ops \\ py_ops
447
  HUnit.assertBool ("Missing OpCodes from the Haskell code:\n" ++
448
                    unlines extra_py) (null extra_py)
449
  HUnit.assertBool ("Extra OpCodes in the Haskell code code:\n" ++
450
                    unlines extra_hs) (null extra_hs)
451

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

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

    
540
-- | Checks that setOpComment works correctly.
541
prop_setOpComment :: OpCodes.MetaOpCode -> String -> Property
542
prop_setOpComment op comment =
543
  let (OpCodes.MetaOpCode common _) = OpCodes.setOpComment comment op
544
  in OpCodes.opComment common ==? Just comment
545

    
546
-- | Tests wrong tag object building (cluster takes only jsnull, the
547
-- other take a string, so we test the opposites).
548
case_TagObject_fail :: Assertion
549
case_TagObject_fail =
550
  mapM_ (\(t, j) -> assertEqual (show t ++ "/" ++ J.encode j) Nothing $
551
                    tagObjectFrom t j)
552
    [ (TagTypeCluster,  J.showJSON "abc")
553
    , (TagTypeInstance, J.JSNull)
554
    , (TagTypeNode,     J.JSNull)
555
    , (TagTypeGroup,    J.JSNull)
556
    ]
557

    
558
-- | Tests wrong (negative) disk index.
559
prop_mkDiskIndex_fail :: QuickCheck.Positive Int -> Property
560
prop_mkDiskIndex_fail (Positive i) =
561
  case mkDiskIndex (negate i) of
562
    Bad msg -> printTestCase "error message " $
563
               "Invalid value" `isPrefixOf` msg
564
    Ok v -> failTest $ "Succeeded to build disk index '" ++ show v ++
565
                       "' from negative value " ++ show (negate i)
566

    
567
-- | Tests a few invalid 'readRecreateDisks' cases.
568
case_readRecreateDisks_fail :: Assertion
569
case_readRecreateDisks_fail = do
570
  assertBool "null" $
571
    isJsonError (J.readJSON J.JSNull::J.Result RecreateDisksInfo)
572
  assertBool "string" $
573
    isJsonError (J.readJSON (J.showJSON "abc")::J.Result RecreateDisksInfo)
574

    
575
-- | Tests a few invalid 'readDdmOldChanges' cases.
576
case_readDdmOldChanges_fail :: Assertion
577
case_readDdmOldChanges_fail = do
578
  assertBool "null" $
579
    isJsonError (J.readJSON J.JSNull::J.Result DdmOldChanges)
580
  assertBool "string" $
581
    isJsonError (J.readJSON (J.showJSON "abc")::J.Result DdmOldChanges)
582

    
583
-- | Tests a few invalid 'readExportTarget' cases.
584
case_readExportTarget_fail :: Assertion
585
case_readExportTarget_fail = do
586
  assertBool "null" $
587
    isJsonError (J.readJSON J.JSNull::J.Result ExportTarget)
588
  assertBool "int" $
589
    isJsonError (J.readJSON (J.showJSON (5::Int))::J.Result ExportTarget)
590

    
591
testSuite "OpCodes"
592
            [ 'prop_serialization
593
            , 'case_AllDefined
594
            , 'case_py_compat_types
595
            , 'case_py_compat_fields
596
            , 'prop_setOpComment
597
            , 'case_TagObject_fail
598
            , 'prop_mkDiskIndex_fail
599
            , 'case_readRecreateDisks_fail
600
            , 'case_readDdmOldChanges_fail
601
            , 'case_readExportTarget_fail
602
            ]