Revision 0b288282

b/htest/Test/Ganeti/Network.hs
3 3

  
4 4
module Test.Ganeti.Network
5 5
  ( testNetwork
6
  , genBitStringMaxLen
7
  , genNetworkType
6 8
  ) where
7 9

  
8 10
import Test.QuickCheck
9 11

  
10
import Control.Monad
11

  
12 12
import Ganeti.Network as Network
13 13
import Ganeti.Objects as Objects
14
import Ganeti.Types
15 14

  
16
import Test.Ganeti.Query.Language (genJSValue)
15
import Test.Ganeti.Objects
16
  ( genBitStringMaxLen
17
  , genNetworkType
18
  , genValidNetwork )
17 19
import Test.Ganeti.TestHelper
18 20
import Test.Ganeti.TestCommon
19 21

  
20 22
import qualified Data.Vector.Unboxed as V
21
import qualified Data.Set as S
22 23

  
23 24
-- * Generators and arbitrary instances
24 25

  
25
-- | Generate an arbitrary string consisting of '0' and '1' of the given length.
26
genBitString :: Int -> Gen String
27
genBitString len = vectorOf len (elements "01")
28

  
29
-- | Generate an arbitrary string consisting of '0' and '1' of the maximum given
30
-- length.
31
genBitStringMaxLen :: Int -> Gen String
32
genBitStringMaxLen maxLen = choose (0, maxLen) >>= genBitString
33

  
34
-- | Generates an arbitrary bit vector of the given length.
35
genBitVector :: Int -> Gen (V.Vector Bool)
36
genBitVector len = do
37
  boolList <- vector len::Gen [Bool]
38
  return $ V.fromList boolList
39

  
40
-- | Generates a network instance with bit vectors of the given lengths for
41
-- reservations and external reservations.
42
genValidNetwork :: Int -> Gen Objects.Network
43
genValidNetwork maxLenBitStr = do
44
  lenBitStr <- choose (0, maxLenBitStr)
45
  name <- genName >>= mkNonEmpty
46
  network_type <- genMaybe genNetworkType
47
  mac_prefix <- genMaybe genName
48
  fam <- arbitrary
49
  net <- genName >>= mkNonEmpty
50
  net6 <- genMaybe genName
51
  gateway <- genMaybe genName
52
  gateway6 <- genMaybe genName
53
  size <- genMaybe genJSValue
54
  res <- liftM Just (genBitString lenBitStr)
55
  ext_res <- liftM Just (genBitString lenBitStr)
56
  let n = Network name network_type mac_prefix fam net net6 gateway
57
          gateway6 size res ext_res 0 S.empty
58
  return n
59

  
60
-- | Generates an arbitrary network type.
61
genNetworkType :: Gen NetworkType
62
genNetworkType = elements [ PrivateNetwork, PublicNetwork ]
63

  
64
-- | Network instances are generated arbitrarily only with short bit strings to
65
-- not slow down the test execution too much.
66
instance Arbitrary Objects.Network where
67
  arbitrary = genValidNetwork 256
68

  
69 26
-- | Generates address pools. The size of the network is intentionally
70 27
-- decoupled from the size of the bit vectors, to avoid slowing down
71 28
-- the tests by generating unnecessary bit strings.
72 29
genAddressPool :: Int -> Gen AddressPool
73 30
genAddressPool maxLenBitVec = do
74
  net <- arbitrary
31
  -- Generating networks with netmask of minimum /24 to avoid too long
32
  -- bit strings being generated.
33
  net <- genValidNetwork
75 34
  lenBitVec <- choose (0, maxLenBitVec)
76 35
  res <- genBitVector lenBitVec
77 36
  ext_res <- genBitVector lenBitVec
......
79 38
                     , reservations = res
80 39
                     , extReservations = ext_res }
81 40

  
41
-- | Generates an arbitrary bit vector of the given length.
42
genBitVector :: Int -> Gen (V.Vector Bool)
43
genBitVector len = do
44
  boolList <- vector len::Gen [Bool]
45
  return $ V.fromList boolList
46

  
82 47
instance Arbitrary AddressPool where
83 48
  arbitrary = genAddressPool ((2::Int)^(8::Int))
84 49

  
b/htest/Test/Ganeti/Objects.hs
30 30
  ( testObjects
31 31
  , Node(..)
32 32
  , genEmptyCluster
33
  , genValidNetwork
34
  , genNetworkType
35
  , genBitStringMaxLen
33 36
  ) where
34 37

  
35 38
import Test.QuickCheck
39
import qualified Test.HUnit as HUnit
36 40

  
37 41
import Control.Applicative
42
import Control.Monad
43
import Data.Char
38 44
import qualified Data.Map as Map
39 45
import qualified Data.Set as Set
46
import qualified Text.JSON as J
40 47

  
41 48
import Test.Ganeti.Query.Language (genJSValue)
42 49
import Test.Ganeti.TestHelper
......
44 51
import Test.Ganeti.Types ()
45 52

  
46 53
import qualified Ganeti.Constants as C
54
import Ganeti.Network
47 55
import Ganeti.Objects as Objects
48 56
import Ganeti.JSON
57
import Ganeti.Types
49 58

  
50 59
{-# ANN module "HLint: ignore Use camelCase" #-}
51 60

  
......
164 173
$(genArbitrary ''Cluster)
165 174

  
166 175
instance Arbitrary Network where
167
  arbitrary = Network <$>
168
                        -- name
169
                        arbitrary
170
                        -- network_type
171
                        <*> arbitrary
172
                        -- mac_prefix
173
                        <*> arbitrary
174
                        -- family
175
                        <*> arbitrary
176
                        -- network
177
                        <*> arbitrary
178
                        -- network6
179
                        <*> arbitrary
180
                        -- gateway
181
                        <*> arbitrary
182
                        -- gateway6
183
                        <*> arbitrary
184
                        -- size
185
                        <*> genMaybe genJSValue
186
                        -- reservations
187
                        <*> arbitrary
188
                        -- external reservations
189
                        <*> arbitrary
190
                        -- serial
191
                        <*> arbitrary
192
                        -- tags
193
                        <*> (Set.fromList <$> genTags)
176
  arbitrary = genValidNetwork
177

  
178
-- | Generates a network instance with minimum netmasks of /24. Generating
179
-- bigger networks slows down the tests, because long bit strings are generated
180
-- for the reservations.
181
genValidNetwork :: Gen Objects.Network
182
genValidNetwork = do
183
  -- generate netmask for the IPv4 network
184
  netmask <- choose (24::Int, 30)
185
  name <- genName >>= mkNonEmpty
186
  network_type <- genMaybe genNetworkType
187
  mac_prefix <- genMaybe genName
188
  fam <- arbitrary
189
  net <- genIp4NetWithNetmask netmask
190
  net6 <- genMaybe genIp6Net
191
  gateway <- genMaybe genIp4AddrStr
192
  gateway6 <- genMaybe genIp6Addr
193
  size <- genMaybe genJSValue
194
  res <- liftM Just (genBitString $ netmask2NumHosts netmask)
195
  ext_res <- liftM Just (genBitString $ netmask2NumHosts netmask)
196
  let n = Network name network_type mac_prefix fam net net6 gateway
197
          gateway6 size res ext_res 0 Set.empty
198
  return n
199

  
200
-- | Generates an arbitrary network type.
201
genNetworkType :: Gen NetworkType
202
genNetworkType = elements [ PrivateNetwork, PublicNetwork ]
203

  
204
-- | Generate an arbitrary string consisting of '0' and '1' of the given length.
205
genBitString :: Int -> Gen String
206
genBitString len = vectorOf len (elements "01")
207

  
208
-- | Generate an arbitrary string consisting of '0' and '1' of the maximum given
209
-- length.
210
genBitStringMaxLen :: Int -> Gen String
211
genBitStringMaxLen maxLen = choose (0, maxLen) >>= genBitString
194 212

  
195 213
-- | Generator for config data with an empty cluster (no instances),
196 214
-- with N defined nodes.
......
261 279
prop_Config_serialisation =
262 280
  forAll (choose (0, maxNodes `div` 4) >>= genEmptyCluster) testSerialisation
263 281

  
282
-- | Custom HUnit test to check the correspondence between Haskell-generated
283
-- networks and their Python decoded, validated and re-encoded version.
284
-- For the technical background of this unit test, check the documentation
285
-- of "case_py_compat_types" of htest/Test/Ganeti/Opcodes.hs
286
case_py_compat_networks :: HUnit.Assertion
287
case_py_compat_networks = do
288
  let num_networks = 500::Int
289
  sample_networks <- sample' (vectorOf num_networks genValidNetwork)
290
  let networks = head sample_networks
291
      networks_with_properties = map getNetworkProperties networks
292
      serialized = J.encode networks
293
  -- check for non-ASCII fields, usually due to 'arbitrary :: String'
294
  mapM_ (\net -> when (any (not . isAscii) (J.encode net)) .
295
                 HUnit.assertFailure $
296
                 "Network has non-ASCII fields: " ++ show net
297
        ) networks
298
  py_stdout <-
299
    runPython "from ganeti import network\n\
300
              \from ganeti import objects\n\
301
              \from ganeti import serializer\n\
302
              \import sys\n\
303
              \net_data = serializer.Load(sys.stdin.read())\n\
304
              \decoded = [objects.Network.FromDict(n) for n in net_data]\n\
305
              \encoded = []\n\
306
              \for net in decoded:\n\
307
              \  a = network.AddressPool(net)\n\
308
              \  encoded.append((a.GetFreeCount(), a.GetReservedCount(), \\\n\
309
              \    net.ToDict()))\n\
310
              \print serializer.Dump(encoded)" serialized
311
    >>= checkPythonResult
312
  let deserialised = J.decode py_stdout::J.Result [(Int, Int, Network)]
313
  decoded <- case deserialised of
314
               J.Ok ops -> return ops
315
               J.Error msg ->
316
                 HUnit.assertFailure ("Unable to decode networks: " ++ msg)
317
                 -- this already raised an expection, but we need it
318
                 -- for proper types
319
                 >> fail "Unable to decode networks"
320
  HUnit.assertEqual "Mismatch in number of returned networks"
321
    (length decoded) (length networks_with_properties)
322
  mapM_ (uncurry (HUnit.assertEqual "Different result after encoding/decoding")
323
        ) $ zip decoded networks_with_properties
324

  
325
-- | Creates a tuple of the given network combined with some of its properties
326
-- to be compared against the same properties generated by the python code.
327
getNetworkProperties :: Network -> (Int, Int, Network)
328
getNetworkProperties net =
329
  let maybePool = createAddressPool net
330
  in  case maybePool of
331
           (Just pool) -> (getFreeCount pool, getReservedCount pool, net)
332
           Nothing -> (-1, -1, net)
333

  
264 334
testSuite "Objects"
265 335
  [ 'prop_fillDict
266 336
  , 'prop_Disk_serialisation
......
268 338
  , 'prop_Network_serialisation
269 339
  , 'prop_Node_serialisation
270 340
  , 'prop_Config_serialisation
341
  , 'case_py_compat_networks
271 342
  ]
b/htest/Test/Ganeti/OpCodes.hs
386 386
genFieldsNE :: Gen [NonEmptyString]
387 387
genFieldsNE = genFields >>= mapM mkNonEmpty
388 388

  
389
-- | Generate an arbitrary IPv4 address in textual form.
390
genIp4Addr :: Gen NonEmptyString
391
genIp4Addr = do
392
  a <- choose (1::Int, 255)
393
  b <- choose (0::Int, 255)
394
  c <- choose (0::Int, 255)
395
  d <- choose (0::Int, 255)
396
  mkNonEmpty $ intercalate "." (map show [a, b, c, d])
397

  
398
-- | Generate an arbitrary IPv4 network address in textual form.
399
genIp4Net :: Gen NonEmptyString
400
genIp4Net = do
401
  netmask <- choose (8::Int, 30)
402
  ip <- genIp4Addr
403
  mkNonEmpty $ fromNonEmpty ip ++ "/" ++ show netmask
404

  
405 389
-- | Generate a 3-byte MAC prefix.
406 390
genMacPrefix :: Gen NonEmptyString
407 391
genMacPrefix = do
b/htest/Test/Ganeti/TestCommon.hs
38 38
import Test.QuickCheck
39 39
import Test.QuickCheck.Monadic
40 40
import qualified Text.JSON as J
41
import Numeric
41 42

  
42 43
import qualified Ganeti.BasicTypes as BasicTypes
44
import Ganeti.Types
43 45

  
44 46
-- * Constants
45 47

  
......
215 217
genSet :: (Ord a, Bounded a, Enum a) => Maybe Int -> Gen (Set.Set a)
216 218
genSet = genSetHelper [minBound..maxBound]
217 219

  
220
-- | Generate an arbitrary IPv4 address in textual form (non empty).
221
genIp4Addr :: Gen NonEmptyString
222
genIp4Addr = genIp4AddrStr >>= mkNonEmpty
223

  
224
-- | Generate an arbitrary IPv4 address in textual form.
225
genIp4AddrStr :: Gen String
226
genIp4AddrStr = do
227
  a <- choose (1::Int, 255)
228
  b <- choose (0::Int, 255)
229
  c <- choose (0::Int, 255)
230
  d <- choose (0::Int, 255)
231
  return $ intercalate "." (map show [a, b, c, d])
232

  
233
-- | Generates an arbitrary IPv4 address with a given netmask in textual form.
234
genIp4NetWithNetmask :: Int -> Gen NonEmptyString
235
genIp4NetWithNetmask netmask = do
236
  ip <- genIp4AddrStr
237
  mkNonEmpty $ ip ++ "/" ++ show netmask
238

  
239
-- | Generate an arbitrary IPv4 network in textual form.
240
genIp4Net :: Gen NonEmptyString
241
genIp4Net = do
242
  netmask <- choose (8::Int, 30)
243
  genIp4NetWithNetmask netmask
244

  
245
-- | Helper function to compute the number of hosts in a network
246
-- given the netmask. (For IPv4 only.)
247
netmask2NumHosts :: Int -> Int
248
netmask2NumHosts n = (2::Int)^((32::Int)-n)
249

  
250
-- | Generates an arbitrary IPv6 network address in textual form.
251
-- The generated address is not simpflified, e. g. an address like
252
-- "2607:f0d0:1002:0051:0000:0000:0000:0004" does not become
253
-- "2607:f0d0:1002:51::4"
254
genIp6Addr :: Gen String
255
genIp6Addr = do
256
  rawIp <- vectorOf 8 $ choose (0::Integer, 65535)
257
  return $ intercalate ":" (map (`showHex` "") rawIp)
258

  
259
-- | Generates an arbitrary IPv6 network in textual form.
260
genIp6Net :: Gen String
261
genIp6Net = do
262
  netmask <- choose (8::Int, 126)
263
  ip <- genIp6Addr
264
  return $ ip ++ "/" ++ show netmask
265

  
218 266
-- * Helper functions
219 267

  
220 268
-- | Checks for serialisation idempotence.

Also available in: Unified diff