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