Statistics
| Branch: | Tag: | Revision:

root / test / hs / Test / Ganeti / OpCodes.hs @ 42fda604

History | View | Annotate | Download (30.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 (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
$(genArbitrary ''ImportExportCompression)
84

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

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

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

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

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

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

    
117
instance Arbitrary ExportTarget where
118
  arbitrary = oneof [ ExportTargetLocal <$> genNodeNameNE
119
                    , ExportTargetRemote <$> pure []
120
                    ]
121

    
122
instance Arbitrary OpCodes.OpCode where
123
  arbitrary = do
124
    op_id <- elements OpCodes.allOpIDs
125
    case op_id of
126
      "OP_TEST_DELAY" ->
127
        OpCodes.OpTestDelay <$> arbitrary <*> arbitrary <*>
128
          genNodeNamesNE <*> return Nothing <*> arbitrary
129
      "OP_INSTANCE_REPLACE_DISKS" ->
130
        OpCodes.OpInstanceReplaceDisks <$> genFQDN <*> return Nothing <*>
131
          arbitrary <*> arbitrary <*> arbitrary <*> genDiskIndices <*>
132
          genMaybe genNodeNameNE <*> return Nothing <*> genMaybe genNameNE
133
      "OP_INSTANCE_FAILOVER" ->
134
        OpCodes.OpInstanceFailover <$> genFQDN <*> return Nothing <*>
135
        arbitrary <*> arbitrary <*> genMaybe genNodeNameNE <*>
136
        return Nothing <*> arbitrary <*> arbitrary <*> genMaybe genNameNE
137
      "OP_INSTANCE_MIGRATE" ->
138
        OpCodes.OpInstanceMigrate <$> genFQDN <*> return Nothing <*>
139
          arbitrary <*> arbitrary <*> genMaybe genNodeNameNE <*>
140
          return Nothing <*> arbitrary <*> arbitrary <*> arbitrary <*>
141
          genMaybe genNameNE <*> arbitrary
142
      "OP_TAGS_GET" ->
143
        arbitraryOpTagsGet
144
      "OP_TAGS_SEARCH" ->
145
        OpCodes.OpTagsSearch <$> genNameNE
146
      "OP_TAGS_SET" ->
147
        arbitraryOpTagsSet
148
      "OP_TAGS_DEL" ->
149
        arbitraryOpTagsDel
150
      "OP_CLUSTER_POST_INIT" -> pure OpCodes.OpClusterPostInit
151
      "OP_CLUSTER_RENEW_CRYPTO" -> pure OpCodes.OpClusterRenewCrypto
152
      "OP_CLUSTER_DESTROY" -> pure OpCodes.OpClusterDestroy
153
      "OP_CLUSTER_QUERY" -> pure OpCodes.OpClusterQuery
154
      "OP_CLUSTER_VERIFY" ->
155
        OpCodes.OpClusterVerify <$> arbitrary <*> arbitrary <*>
156
          genListSet Nothing <*> genListSet Nothing <*> arbitrary <*>
157
          genMaybe genNameNE
158
      "OP_CLUSTER_VERIFY_CONFIG" ->
159
        OpCodes.OpClusterVerifyConfig <$> arbitrary <*> arbitrary <*>
160
          genListSet Nothing <*> arbitrary
161
      "OP_CLUSTER_VERIFY_GROUP" ->
162
        OpCodes.OpClusterVerifyGroup <$> genNameNE <*> arbitrary <*>
163
          arbitrary <*> genListSet Nothing <*> genListSet Nothing <*> arbitrary
164
      "OP_CLUSTER_VERIFY_DISKS" -> pure OpCodes.OpClusterVerifyDisks
165
      "OP_GROUP_VERIFY_DISKS" ->
166
        OpCodes.OpGroupVerifyDisks <$> genNameNE
167
      "OP_CLUSTER_REPAIR_DISK_SIZES" ->
168
        OpCodes.OpClusterRepairDiskSizes <$> genNodeNamesNE
169
      "OP_CLUSTER_CONFIG_QUERY" ->
170
        OpCodes.OpClusterConfigQuery <$> genFieldsNE
171
      "OP_CLUSTER_RENAME" ->
172
        OpCodes.OpClusterRename <$> genNameNE
173
      "OP_CLUSTER_SET_PARAMS" ->
174
        OpCodes.OpClusterSetParams
175
          <$> arbitrary                    -- force
176
          <*> emptyMUD                     -- hv_state
177
          <*> emptyMUD                     -- disk_state
178
          <*> arbitrary                    -- vg_name
179
          <*> genMaybe arbitrary           -- enabled_hypervisors
180
          <*> genMaybe genEmptyContainer   -- hvparams
181
          <*> emptyMUD                     -- beparams
182
          <*> genMaybe genEmptyContainer   -- os_hvp
183
          <*> genMaybe genEmptyContainer   -- osparams
184
          <*> genMaybe genEmptyContainer   -- osparams_private_cluster
185
          <*> genMaybe genEmptyContainer   -- diskparams
186
          <*> genMaybe arbitrary           -- candidate_pool_size
187
          <*> genMaybe arbitrary           -- max_running_jobs
188
          <*> arbitrary                    -- uid_pool
189
          <*> arbitrary                    -- add_uids
190
          <*> arbitrary                    -- remove_uids
191
          <*> arbitrary                    -- maintain_node_health
192
          <*> arbitrary                    -- prealloc_wipe_disks
193
          <*> arbitrary                    -- nicparams
194
          <*> emptyMUD                     -- ndparams
195
          <*> emptyMUD                     -- ipolicy
196
          <*> arbitrary                    -- drbd_helper
197
          <*> arbitrary                    -- default_iallocator
198
          <*> emptyMUD                     -- default_iallocator_params
199
          <*> arbitrary                    -- master_netdev
200
          <*> arbitrary                    -- master_netmask
201
          <*> arbitrary                    -- reserved_lvs
202
          <*> arbitrary                    -- hidden_os
203
          <*> arbitrary                    -- blacklisted_os
204
          <*> arbitrary                    -- use_external_mip_script
205
          <*> arbitrary                    -- enabled_disk_templates
206
          <*> arbitrary                    -- modify_etc_hosts
207
          <*> genMaybe genName             -- file_storage_dir
208
          <*> genMaybe genName             -- shared_file_storage_dir
209
          <*> genMaybe genName             -- gluster_file_storage_dir
210
          <*> arbitrary                    -- instance_communication_network
211
      "OP_CLUSTER_REDIST_CONF" -> pure OpCodes.OpClusterRedistConf
212
      "OP_CLUSTER_ACTIVATE_MASTER_IP" ->
213
        pure OpCodes.OpClusterActivateMasterIp
214
      "OP_CLUSTER_DEACTIVATE_MASTER_IP" ->
215
        pure OpCodes.OpClusterDeactivateMasterIp
216
      "OP_QUERY" ->
217
        OpCodes.OpQuery <$> arbitrary <*> arbitrary <*> arbitrary <*>
218
        pure Nothing
219
      "OP_QUERY_FIELDS" ->
220
        OpCodes.OpQueryFields <$> arbitrary <*> arbitrary
221
      "OP_OOB_COMMAND" ->
222
        OpCodes.OpOobCommand <$> genNodeNamesNE <*> return Nothing <*>
223
          arbitrary <*> arbitrary <*> arbitrary <*>
224
          (arbitrary `suchThat` (>0))
225
      "OP_NODE_REMOVE" ->
226
        OpCodes.OpNodeRemove <$> genNodeNameNE <*> return Nothing
227
      "OP_NODE_ADD" ->
228
        OpCodes.OpNodeAdd <$> genNodeNameNE <*> emptyMUD <*> emptyMUD <*>
229
          genMaybe genNameNE <*> genMaybe genNameNE <*> arbitrary <*>
230
          genMaybe genNameNE <*> arbitrary <*> arbitrary <*> emptyMUD
231
      "OP_NODE_QUERYVOLS" ->
232
        OpCodes.OpNodeQueryvols <$> arbitrary <*> genNodeNamesNE
233
      "OP_NODE_QUERY_STORAGE" ->
234
        OpCodes.OpNodeQueryStorage <$> arbitrary <*> arbitrary <*>
235
          genNodeNamesNE <*> genMaybe genNameNE
236
      "OP_NODE_MODIFY_STORAGE" ->
237
        OpCodes.OpNodeModifyStorage <$> genNodeNameNE <*> return Nothing <*>
238
          arbitrary <*> genMaybe genNameNE <*> pure emptyJSObject
239
      "OP_REPAIR_NODE_STORAGE" ->
240
        OpCodes.OpRepairNodeStorage <$> genNodeNameNE <*> return Nothing <*>
241
          arbitrary <*> genMaybe genNameNE <*> arbitrary
242
      "OP_NODE_SET_PARAMS" ->
243
        OpCodes.OpNodeSetParams <$> genNodeNameNE <*> return Nothing <*>
244
          arbitrary <*> emptyMUD <*> emptyMUD <*> arbitrary <*> arbitrary <*>
245
          arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*>
246
          genMaybe genNameNE <*> emptyMUD <*> arbitrary
247
      "OP_NODE_POWERCYCLE" ->
248
        OpCodes.OpNodePowercycle <$> genNodeNameNE <*> return Nothing <*>
249
          arbitrary
250
      "OP_NODE_MIGRATE" ->
251
        OpCodes.OpNodeMigrate <$> genNodeNameNE <*> return Nothing <*>
252
          arbitrary <*> arbitrary <*> genMaybe genNodeNameNE <*>
253
          return Nothing <*> arbitrary <*> arbitrary <*> genMaybe genNameNE
254
      "OP_NODE_EVACUATE" ->
255
        OpCodes.OpNodeEvacuate <$> arbitrary <*> genNodeNameNE <*>
256
          return Nothing <*> genMaybe genNodeNameNE <*> return Nothing <*>
257
          genMaybe genNameNE <*> arbitrary
258
      "OP_INSTANCE_CREATE" ->
259
        OpCodes.OpInstanceCreate
260
          <$> genFQDN                         -- instance_name
261
          <*> arbitrary                       -- force_variant
262
          <*> arbitrary                       -- wait_for_sync
263
          <*> arbitrary                       -- name_check
264
          <*> arbitrary                       -- ignore_ipolicy
265
          <*> arbitrary                       -- opportunistic_locking
266
          <*> pure emptyJSObject              -- beparams
267
          <*> arbitrary                       -- disks
268
          <*> arbitrary                       -- disk_template
269
          <*> arbitrary                       -- file_driver
270
          <*> genMaybe genNameNE              -- file_storage_dir
271
          <*> pure emptyJSObject              -- hvparams
272
          <*> arbitrary                       -- hypervisor
273
          <*> genMaybe genNameNE              -- iallocator
274
          <*> arbitrary                       -- identify_defaults
275
          <*> arbitrary                       -- ip_check
276
          <*> arbitrary                       -- conflicts_check
277
          <*> arbitrary                       -- mode
278
          <*> arbitrary                       -- nics
279
          <*> arbitrary                       -- no_install
280
          <*> pure emptyJSObject              -- osparams
281
          <*> genMaybe arbitraryPrivateJSObj  -- osparams_private
282
          <*> genMaybe arbitraryPrivateJSObj  -- osparams_secret
283
          <*> genMaybe genNameNE              -- os_type
284
          <*> genMaybe genNodeNameNE          -- pnode
285
          <*> return Nothing                  -- pnode_uuid
286
          <*> genMaybe genNodeNameNE          -- snode
287
          <*> return Nothing                  -- snode_uuid
288
          <*> genMaybe (pure [])              -- source_handshake
289
          <*> genMaybe genNodeNameNE          -- source_instance_name
290
          <*> arbitrary                       -- source_shutdown_timeout
291
          <*> genMaybe genNodeNameNE          -- source_x509_ca
292
          <*> return Nothing                  -- src_node
293
          <*> genMaybe genNodeNameNE          -- src_node_uuid
294
          <*> genMaybe genNameNE              -- src_path
295
          <*> arbitrary                       -- compress
296
          <*> arbitrary                       -- start
297
          <*> (genTags >>= mapM mkNonEmpty)   -- tags
298
          <*> arbitrary                       -- instance_communication
299
      "OP_INSTANCE_MULTI_ALLOC" ->
300
        OpCodes.OpInstanceMultiAlloc <$> arbitrary <*> genMaybe genNameNE <*>
301
        pure []
302
      "OP_INSTANCE_REINSTALL" ->
303
        OpCodes.OpInstanceReinstall <$> genFQDN <*> return Nothing <*>
304
          arbitrary <*> genMaybe genNameNE <*> genMaybe (pure emptyJSObject)
305
          <*> genMaybe arbitraryPrivateJSObj <*> genMaybe arbitraryPrivateJSObj
306
      "OP_INSTANCE_REMOVE" ->
307
        OpCodes.OpInstanceRemove <$> genFQDN <*> return Nothing <*>
308
          arbitrary <*> arbitrary
309
      "OP_INSTANCE_RENAME" ->
310
        OpCodes.OpInstanceRename <$> genFQDN <*> return Nothing <*>
311
          genNodeNameNE <*> arbitrary <*> arbitrary
312
      "OP_INSTANCE_STARTUP" ->
313
        OpCodes.OpInstanceStartup <$> genFQDN <*> return Nothing <*>
314
          arbitrary <*> arbitrary <*> pure emptyJSObject <*>
315
          pure emptyJSObject <*> arbitrary <*> arbitrary
316
      "OP_INSTANCE_SHUTDOWN" ->
317
        OpCodes.OpInstanceShutdown <$> genFQDN <*> return Nothing <*>
318
          arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
319
      "OP_INSTANCE_REBOOT" ->
320
        OpCodes.OpInstanceReboot <$> genFQDN <*> return Nothing <*>
321
          arbitrary <*> arbitrary <*> arbitrary
322
      "OP_INSTANCE_MOVE" ->
323
        OpCodes.OpInstanceMove <$> genFQDN <*> return Nothing <*>
324
          arbitrary <*> arbitrary <*> genNodeNameNE <*> return Nothing <*>
325
          arbitrary <*> arbitrary
326
      "OP_INSTANCE_CONSOLE" -> OpCodes.OpInstanceConsole <$> genFQDN <*>
327
          return Nothing
328
      "OP_INSTANCE_ACTIVATE_DISKS" ->
329
        OpCodes.OpInstanceActivateDisks <$> genFQDN <*> return Nothing <*>
330
          arbitrary <*> arbitrary
331
      "OP_INSTANCE_DEACTIVATE_DISKS" ->
332
        OpCodes.OpInstanceDeactivateDisks <$> genFQDN <*> return Nothing <*>
333
          arbitrary
334
      "OP_INSTANCE_RECREATE_DISKS" ->
335
        OpCodes.OpInstanceRecreateDisks <$> genFQDN <*> return Nothing <*>
336
          arbitrary <*> genNodeNamesNE <*> return Nothing <*>
337
          genMaybe genNameNE
338
      "OP_INSTANCE_QUERY_DATA" ->
339
        OpCodes.OpInstanceQueryData <$> arbitrary <*>
340
          genNodeNamesNE <*> arbitrary
341
      "OP_INSTANCE_SET_PARAMS" ->
342
        OpCodes.OpInstanceSetParams
343
          <$> genFQDN                         -- instance_name
344
          <*> return Nothing                  -- instance_uuid
345
          <*> arbitrary                       -- force
346
          <*> arbitrary                       -- force_variant
347
          <*> arbitrary                       -- ignore_ipolicy
348
          <*> arbitrary                       -- nics
349
          <*> arbitrary                       -- disks
350
          <*> pure emptyJSObject              -- beparams
351
          <*> arbitrary                       -- runtime_mem
352
          <*> pure emptyJSObject              -- hvparams
353
          <*> arbitrary                       -- disk_template
354
          <*> genMaybe genNodeNameNE          -- pnode
355
          <*> return Nothing                  -- pnode_uuid
356
          <*> genMaybe genNodeNameNE          -- remote_node
357
          <*> return Nothing                  -- remote_node_uuid
358
          <*> genMaybe genNameNE              -- os_name
359
          <*> pure emptyJSObject              -- osparams
360
          <*> genMaybe arbitraryPrivateJSObj  -- osparams_private
361
          <*> arbitrary                       -- wait_for_sync
362
          <*> arbitrary                       -- offline
363
          <*> arbitrary                       -- conflicts_check
364
          <*> arbitrary                       -- hotplug
365
          <*> arbitrary                       -- hotplug_if_possible
366
      "OP_INSTANCE_GROW_DISK" ->
367
        OpCodes.OpInstanceGrowDisk <$> genFQDN <*> return Nothing <*>
368
          arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
369
      "OP_INSTANCE_CHANGE_GROUP" ->
370
        OpCodes.OpInstanceChangeGroup <$> genFQDN <*> return Nothing <*>
371
          arbitrary <*> genMaybe genNameNE <*>
372
          genMaybe (resize maxNodes (listOf genNameNE))
373
      "OP_GROUP_ADD" ->
374
        OpCodes.OpGroupAdd <$> genNameNE <*> arbitrary <*>
375
          emptyMUD <*> genMaybe genEmptyContainer <*>
376
          emptyMUD <*> emptyMUD <*> emptyMUD
377
      "OP_GROUP_ASSIGN_NODES" ->
378
        OpCodes.OpGroupAssignNodes <$> genNameNE <*> arbitrary <*>
379
          genNodeNamesNE <*> return Nothing
380
      "OP_GROUP_SET_PARAMS" ->
381
        OpCodes.OpGroupSetParams <$> genNameNE <*> arbitrary <*>
382
          emptyMUD <*> genMaybe genEmptyContainer <*>
383
          emptyMUD <*> emptyMUD <*> emptyMUD
384
      "OP_GROUP_REMOVE" ->
385
        OpCodes.OpGroupRemove <$> genNameNE
386
      "OP_GROUP_RENAME" ->
387
        OpCodes.OpGroupRename <$> genNameNE <*> genNameNE
388
      "OP_GROUP_EVACUATE" ->
389
        OpCodes.OpGroupEvacuate <$> genNameNE <*> arbitrary <*>
390
          genMaybe genNameNE <*> genMaybe genNamesNE
391
      "OP_OS_DIAGNOSE" ->
392
        OpCodes.OpOsDiagnose <$> genFieldsNE <*> genNamesNE
393
      "OP_EXT_STORAGE_DIAGNOSE" ->
394
        OpCodes.OpOsDiagnose <$> genFieldsNE <*> genNamesNE
395
      "OP_BACKUP_PREPARE" ->
396
        OpCodes.OpBackupPrepare <$> genFQDN <*> return Nothing <*> arbitrary
397
      "OP_BACKUP_EXPORT" ->
398
        OpCodes.OpBackupExport <$> genFQDN <*> return Nothing <*>
399
          arbitrary <*> arbitrary <*> arbitrary <*> return Nothing <*>
400
          arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*>
401
          genMaybe (pure []) <*> genMaybe genNameNE
402
      "OP_BACKUP_REMOVE" ->
403
        OpCodes.OpBackupRemove <$> genFQDN <*> return Nothing
404
      "OP_TEST_ALLOCATOR" ->
405
        OpCodes.OpTestAllocator <$> arbitrary <*> arbitrary <*>
406
          genNameNE <*> genMaybe (pure []) <*> genMaybe (pure []) <*>
407
          arbitrary <*> genMaybe genNameNE <*>
408
          (genTags >>= mapM mkNonEmpty) <*>
409
          arbitrary <*> arbitrary <*> genMaybe genNameNE <*>
410
          arbitrary <*> genMaybe genNodeNamesNE <*> arbitrary <*>
411
          genMaybe genNamesNE <*> arbitrary <*> arbitrary
412
      "OP_TEST_JQUEUE" ->
413
        OpCodes.OpTestJqueue <$> arbitrary <*> arbitrary <*>
414
          resize 20 (listOf genFQDN) <*> arbitrary
415
      "OP_TEST_DUMMY" ->
416
        OpCodes.OpTestDummy <$> pure J.JSNull <*> pure J.JSNull <*>
417
          pure J.JSNull <*> pure J.JSNull
418
      "OP_NETWORK_ADD" ->
419
        OpCodes.OpNetworkAdd <$> genNameNE <*> genIPv4Network <*>
420
          genMaybe genIPv4Address <*> pure Nothing <*> pure Nothing <*>
421
          genMaybe genMacPrefix <*> genMaybe (listOf genIPv4Address) <*>
422
          arbitrary <*> (genTags >>= mapM mkNonEmpty)
423
      "OP_NETWORK_REMOVE" ->
424
        OpCodes.OpNetworkRemove <$> genNameNE <*> arbitrary
425
      "OP_NETWORK_SET_PARAMS" ->
426
        OpCodes.OpNetworkSetParams <$> genNameNE <*>
427
          genMaybe genIPv4Address <*> pure Nothing <*> pure Nothing <*>
428
          genMaybe genMacPrefix <*> genMaybe (listOf genIPv4Address) <*>
429
          genMaybe (listOf genIPv4Address)
430
      "OP_NETWORK_CONNECT" ->
431
        OpCodes.OpNetworkConnect <$> genNameNE <*> genNameNE <*>
432
          arbitrary <*> genNameNE <*> arbitrary
433
      "OP_NETWORK_DISCONNECT" ->
434
        OpCodes.OpNetworkDisconnect <$> genNameNE <*> genNameNE
435
      "OP_RESTRICTED_COMMAND" ->
436
        OpCodes.OpRestrictedCommand <$> arbitrary <*> genNodeNamesNE <*>
437
          return Nothing <*> genNameNE
438
      _ -> fail $ "Undefined arbitrary for opcode " ++ op_id
439

    
440
-- | Generates one element of a reason trail
441
genReasonElem :: Gen ReasonElem
442
genReasonElem = (,,) <$> genFQDN <*> genFQDN <*> arbitrary
443

    
444
-- | Generates a reason trail
445
genReasonTrail :: Gen ReasonTrail
446
genReasonTrail = do
447
  size <- choose (0, 10)
448
  vectorOf size genReasonElem
449

    
450
instance Arbitrary OpCodes.CommonOpParams where
451
  arbitrary = OpCodes.CommonOpParams <$> arbitrary <*> arbitrary <*>
452
                arbitrary <*> resize 5 arbitrary <*> genMaybe genName <*>
453
                genReasonTrail
454

    
455
-- * Helper functions
456

    
457
-- | Empty JSObject.
458
emptyJSObject :: J.JSObject J.JSValue
459
emptyJSObject = J.toJSObject []
460

    
461
-- | Empty maybe unchecked dictionary.
462
emptyMUD :: Gen (Maybe (J.JSObject J.JSValue))
463
emptyMUD = genMaybe $ pure emptyJSObject
464

    
465
-- | Generates an empty container.
466
genEmptyContainer :: (Ord a) => Gen (GenericContainer a b)
467
genEmptyContainer = pure . GenericContainer $ Map.fromList []
468

    
469
-- | Generates list of disk indices.
470
genDiskIndices :: Gen [DiskIndex]
471
genDiskIndices = do
472
  cnt <- choose (0, C.maxDisks)
473
  genUniquesList cnt arbitrary
474

    
475
-- | Generates a list of node names.
476
genNodeNames :: Gen [String]
477
genNodeNames = resize maxNodes (listOf genFQDN)
478

    
479
-- | Generates a list of node names in non-empty string type.
480
genNodeNamesNE :: Gen [NonEmptyString]
481
genNodeNamesNE = genNodeNames >>= mapM mkNonEmpty
482

    
483
-- | Gets a node name in non-empty type.
484
genNodeNameNE :: Gen NonEmptyString
485
genNodeNameNE = genFQDN >>= mkNonEmpty
486

    
487
-- | Gets a name (non-fqdn) in non-empty type.
488
genNameNE :: Gen NonEmptyString
489
genNameNE = genName >>= mkNonEmpty
490

    
491
-- | Gets a list of names (non-fqdn) in non-empty type.
492
genNamesNE :: Gen [NonEmptyString]
493
genNamesNE = resize maxNodes (listOf genNameNE)
494

    
495
-- | Returns a list of non-empty fields.
496
genFieldsNE :: Gen [NonEmptyString]
497
genFieldsNE = genFields >>= mapM mkNonEmpty
498

    
499
-- | Generate a 3-byte MAC prefix.
500
genMacPrefix :: Gen NonEmptyString
501
genMacPrefix = do
502
  octets <- vectorOf 3 $ choose (0::Int, 255)
503
  mkNonEmpty . intercalate ":" $ map (printf "%02x") octets
504

    
505
-- | JSObject of arbitrary data.
506
--
507
-- Since JSValue does not implement Arbitrary, I'll simply generate
508
-- (String, String) objects.
509
arbitraryPrivateJSObj :: Gen (J.JSObject (Private J.JSValue))
510
arbitraryPrivateJSObj =
511
  constructor <$> (fromNonEmpty <$> genNameNE)
512
              <*> (fromNonEmpty <$> genNameNE)
513
    where constructor k v = showPrivateJSObject [(k, v)]
514

    
515
-- | Arbitrary instance for MetaOpCode, defined here due to TH ordering.
516
$(genArbitrary ''OpCodes.MetaOpCode)
517

    
518
-- | Small helper to check for a failed JSON deserialisation
519
isJsonError :: J.Result a -> Bool
520
isJsonError (J.Error _) = True
521
isJsonError _           = False
522

    
523
-- * Test cases
524

    
525
-- | Check that opcode serialization is idempotent.
526
prop_serialization :: OpCodes.OpCode -> Property
527
prop_serialization = testSerialisation
528

    
529
-- | Check that Python and Haskell defined the same opcode list.
530
case_AllDefined :: HUnit.Assertion
531
case_AllDefined = do
532
  py_stdout <-
533
     runPython "from ganeti import opcodes\n\
534
               \from ganeti import serializer\n\
535
               \import sys\n\
536
               \print serializer.Dump([opid for opid in opcodes.OP_MAPPING])\n"
537
               ""
538
     >>= checkPythonResult
539
  py_ops <- case J.decode py_stdout::J.Result [String] of
540
               J.Ok ops -> return ops
541
               J.Error msg ->
542
                 HUnit.assertFailure ("Unable to decode opcode names: " ++ msg)
543
                 -- this already raised an expection, but we need it
544
                 -- for proper types
545
                 >> fail "Unable to decode opcode names"
546
  let hs_ops = sort OpCodes.allOpIDs
547
      extra_py = py_ops \\ hs_ops
548
      extra_hs = hs_ops \\ py_ops
549
  HUnit.assertBool ("Missing OpCodes from the Haskell code:\n" ++
550
                    unlines extra_py) (null extra_py)
551
  HUnit.assertBool ("Extra OpCodes in the Haskell code code:\n" ++
552
                    unlines extra_hs) (null extra_hs)
553

    
554
-- | Custom HUnit test case that forks a Python process and checks
555
-- correspondence between Haskell-generated OpCodes and their Python
556
-- decoded, validated and re-encoded version.
557
--
558
-- Note that we have a strange beast here: since launching Python is
559
-- expensive, we don't do this via a usual QuickProperty, since that's
560
-- slow (I've tested it, and it's indeed quite slow). Rather, we use a
561
-- single HUnit assertion, and in it we manually use QuickCheck to
562
-- generate 500 opcodes times the number of defined opcodes, which
563
-- then we pass in bulk to Python. The drawbacks to this method are
564
-- two fold: we cannot control the number of generated opcodes, since
565
-- HUnit assertions don't get access to the test options, and for the
566
-- same reason we can't run a repeatable seed. We should probably find
567
-- a better way to do this, for example by having a
568
-- separately-launched Python process (if not running the tests would
569
-- be skipped).
570
case_py_compat_types :: HUnit.Assertion
571
case_py_compat_types = do
572
  let num_opcodes = length OpCodes.allOpIDs * 100
573
  opcodes <- genSample (vectorOf num_opcodes
574
                                   (arbitrary::Gen OpCodes.MetaOpCode))
575
  let with_sum = map (\o -> (OpCodes.opSummary $
576
                             OpCodes.metaOpCode o, o)) opcodes
577
      serialized = J.encode opcodes
578
  -- check for non-ASCII fields, usually due to 'arbitrary :: String'
579
  mapM_ (\op -> when (any (not . isAscii) (J.encode op)) .
580
                HUnit.assertFailure $
581
                  "OpCode has non-ASCII fields: " ++ show op
582
        ) opcodes
583
  py_stdout <-
584
     runPython "from ganeti import opcodes\n\
585
               \from ganeti import serializer\n\
586
               \import sys\n\
587
               \op_data = serializer.Load(sys.stdin.read())\n\
588
               \decoded = [opcodes.OpCode.LoadOpCode(o) for o in op_data]\n\
589
               \for op in decoded:\n\
590
               \  op.Validate(True)\n\
591
               \encoded = [(op.Summary(), op.__getstate__())\n\
592
               \           for op in decoded]\n\
593
               \print serializer.Dump(\
594
               \  encoded,\
595
               \  private_encoder=serializer.EncodeWithPrivateFields)"
596
               serialized
597
     >>= checkPythonResult
598
  let deserialised =
599
        J.decode py_stdout::J.Result [(String, OpCodes.MetaOpCode)]
600
  decoded <- case deserialised of
601
               J.Ok ops -> return ops
602
               J.Error msg ->
603
                 HUnit.assertFailure ("Unable to decode opcodes: " ++ msg)
604
                 -- this already raised an expection, but we need it
605
                 -- for proper types
606
                 >> fail "Unable to decode opcodes"
607
  HUnit.assertEqual "Mismatch in number of returned opcodes"
608
    (length decoded) (length with_sum)
609
  mapM_ (uncurry (HUnit.assertEqual "Different result after encoding/decoding")
610
        ) $ zip with_sum decoded
611

    
612
-- | Custom HUnit test case that forks a Python process and checks
613
-- correspondence between Haskell OpCodes fields and their Python
614
-- equivalent.
615
case_py_compat_fields :: HUnit.Assertion
616
case_py_compat_fields = do
617
  let hs_fields = sort $ map (\op_id -> (op_id, OpCodes.allOpFields op_id))
618
                         OpCodes.allOpIDs
619
  py_stdout <-
620
     runPython "from ganeti import opcodes\n\
621
               \import sys\n\
622
               \from ganeti import serializer\n\
623
               \fields = [(k, sorted([p[0] for p in v.OP_PARAMS]))\n\
624
               \           for k, v in opcodes.OP_MAPPING.items()]\n\
625
               \print serializer.Dump(fields)" ""
626
     >>= checkPythonResult
627
  let deserialised = J.decode py_stdout::J.Result [(String, [String])]
628
  py_fields <- case deserialised of
629
                 J.Ok v -> return $ sort v
630
                 J.Error msg ->
631
                   HUnit.assertFailure ("Unable to decode op fields: " ++ msg)
632
                   -- this already raised an expection, but we need it
633
                   -- for proper types
634
                   >> fail "Unable to decode op fields"
635
  HUnit.assertEqual "Mismatch in number of returned opcodes"
636
    (length hs_fields) (length py_fields)
637
  HUnit.assertEqual "Mismatch in defined OP_IDs"
638
    (map fst hs_fields) (map fst py_fields)
639
  mapM_ (\((py_id, py_flds), (hs_id, hs_flds)) -> do
640
           HUnit.assertEqual "Mismatch in OP_ID" py_id hs_id
641
           HUnit.assertEqual ("Mismatch in fields for " ++ hs_id)
642
             py_flds hs_flds
643
        ) $ zip hs_fields py_fields
644

    
645
-- | Checks that setOpComment works correctly.
646
prop_setOpComment :: OpCodes.MetaOpCode -> String -> Property
647
prop_setOpComment op comment =
648
  let (OpCodes.MetaOpCode common _) = OpCodes.setOpComment comment op
649
  in OpCodes.opComment common ==? Just comment
650

    
651
-- | Tests wrong (negative) disk index.
652
prop_mkDiskIndex_fail :: QuickCheck.Positive Int -> Property
653
prop_mkDiskIndex_fail (Positive i) =
654
  case mkDiskIndex (negate i) of
655
    Bad msg -> printTestCase "error message " $
656
               "Invalid value" `isPrefixOf` msg
657
    Ok v -> failTest $ "Succeeded to build disk index '" ++ show v ++
658
                       "' from negative value " ++ show (negate i)
659

    
660
-- | Tests a few invalid 'readRecreateDisks' cases.
661
case_readRecreateDisks_fail :: Assertion
662
case_readRecreateDisks_fail = do
663
  assertBool "null" $
664
    isJsonError (J.readJSON J.JSNull::J.Result RecreateDisksInfo)
665
  assertBool "string" $
666
    isJsonError (J.readJSON (J.showJSON "abc")::J.Result RecreateDisksInfo)
667

    
668
-- | Tests a few invalid 'readDdmOldChanges' cases.
669
case_readDdmOldChanges_fail :: Assertion
670
case_readDdmOldChanges_fail = do
671
  assertBool "null" $
672
    isJsonError (J.readJSON J.JSNull::J.Result DdmOldChanges)
673
  assertBool "string" $
674
    isJsonError (J.readJSON (J.showJSON "abc")::J.Result DdmOldChanges)
675

    
676
-- | Tests a few invalid 'readExportTarget' cases.
677
case_readExportTarget_fail :: Assertion
678
case_readExportTarget_fail = do
679
  assertBool "null" $
680
    isJsonError (J.readJSON J.JSNull::J.Result ExportTarget)
681
  assertBool "int" $
682
    isJsonError (J.readJSON (J.showJSON (5::Int))::J.Result ExportTarget)
683

    
684
testSuite "OpCodes"
685
            [ 'prop_serialization
686
            , 'case_AllDefined
687
            , 'case_py_compat_types
688
            , 'case_py_compat_fields
689
            , 'prop_setOpComment
690
            , 'prop_mkDiskIndex_fail
691
            , 'case_readRecreateDisks_fail
692
            , 'case_readDdmOldChanges_fail
693
            , 'case_readExportTarget_fail
694
            ]