1 {-# LANGUAGE TemplateHaskell, TypeSynonymInstances, FlexibleInstances #-}
2 {-# OPTIONS_GHC -fno-warn-orphans #-}
4 {-| Unittests for ganeti-htools.
10 Copyright (C) 2009, 2010, 2011, 2012 Google Inc.
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.
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.
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
29 module Test.Ganeti.Objects
38 import Test.QuickCheck
39 import qualified Test.HUnit as HUnit
41 import Control.Applicative
44 import qualified Data.Map as Map
45 import qualified Data.Set as Set
46 import qualified Text.JSON as J
48 import Test.Ganeti.Query.Language (genJSValue)
49 import Test.Ganeti.TestHelper
50 import Test.Ganeti.TestCommon
51 import Test.Ganeti.Types ()
53 import qualified Ganeti.Constants as C
55 import Ganeti.Objects as Objects
59 {-# ANN module "HLint: ignore Use camelCase" #-}
61 -- * Arbitrary instances
63 $(genArbitrary ''PartialNDParams)
65 instance Arbitrary Node where
66 arbitrary = Node <$> genFQDN <*> genFQDN <*> genFQDN
67 <*> arbitrary <*> arbitrary <*> arbitrary <*> genFQDN
68 <*> arbitrary <*> arbitrary <*> arbitrary <*> arbitrary
69 <*> arbitrary <*> arbitrary <*> genFQDN <*> arbitrary
70 <*> (Set.fromList <$> genTags)
72 $(genArbitrary ''BlockDriver)
74 $(genArbitrary ''DiskMode)
76 instance Arbitrary DiskLogicalId where
77 arbitrary = oneof [ LIDPlain <$> arbitrary <*> arbitrary
78 , LIDDrbd8 <$> genFQDN <*> genFQDN <*> arbitrary
79 <*> arbitrary <*> arbitrary <*> arbitrary
80 , LIDFile <$> arbitrary <*> arbitrary
81 , LIDBlockDev <$> arbitrary <*> arbitrary
82 , LIDRados <$> arbitrary <*> arbitrary
85 -- | 'Disk' 'arbitrary' instance. Since we don't test disk hierarchy
86 -- properties, we only generate disks with no children (FIXME), as
87 -- generating recursive datastructures is a bit more work.
88 instance Arbitrary Disk where
89 arbitrary = Disk <$> arbitrary <*> pure [] <*> arbitrary
90 <*> arbitrary <*> arbitrary
92 -- FIXME: we should generate proper values, >=0, etc., but this is
93 -- hard for partial ones, where all must be wrapped in a 'Maybe'
94 $(genArbitrary ''PartialBeParams)
96 $(genArbitrary ''AdminState)
98 $(genArbitrary ''PartialNicParams)
100 $(genArbitrary ''PartialNic)
102 instance Arbitrary Instance where
105 <$> genFQDN <*> genFQDN <*> genFQDN -- OS name, but...
107 -- FIXME: add non-empty hvparams when they're a proper type
108 <*> pure (GenericContainer Map.empty) <*> arbitrary
109 -- ... and for OSParams
110 <*> pure (GenericContainer Map.empty) <*> arbitrary <*> arbitrary
111 <*> arbitrary <*> arbitrary <*> arbitrary
113 <*> arbitrary <*> arbitrary
119 <*> (Set.fromList <$> genTags)
121 -- | FIXME: This generates completely random data, without normal
123 $(genArbitrary ''PartialISpecParams)
125 -- | FIXME: This generates completely random data, without normal
127 $(genArbitrary ''PartialIPolicy)
129 $(genArbitrary ''FilledISpecParams)
130 $(genArbitrary ''FilledIPolicy)
131 $(genArbitrary ''IpFamily)
132 $(genArbitrary ''FilledNDParams)
133 $(genArbitrary ''FilledNicParams)
134 $(genArbitrary ''FilledBeParams)
136 -- | No real arbitrary instance for 'ClusterHvParams' yet.
137 instance Arbitrary ClusterHvParams where
138 arbitrary = return $ GenericContainer Map.empty
140 -- | No real arbitrary instance for 'OsHvParams' yet.
141 instance Arbitrary OsHvParams where
142 arbitrary = return $ GenericContainer Map.empty
144 instance Arbitrary ClusterNicParams where
145 arbitrary = (GenericContainer . Map.singleton C.ppDefault) <$> arbitrary
147 instance Arbitrary OsParams where
148 arbitrary = (GenericContainer . Map.fromList) <$> arbitrary
150 instance Arbitrary ClusterOsParams where
151 arbitrary = (GenericContainer . Map.fromList) <$> arbitrary
153 instance Arbitrary ClusterBeParams where
154 arbitrary = (GenericContainer . Map.fromList) <$> arbitrary
156 instance Arbitrary TagSet where
157 arbitrary = Set.fromList <$> genTags
159 $(genArbitrary ''Cluster)
161 instance Arbitrary Network where
162 arbitrary = genValidNetwork
164 -- | Generates a network instance with minimum netmasks of /24. Generating
165 -- bigger networks slows down the tests, because long bit strings are generated
166 -- for the reservations.
167 genValidNetwork :: Gen Objects.Network
169 -- generate netmask for the IPv4 network
170 netmask <- choose (24::Int, 30)
171 name <- genName >>= mkNonEmpty
172 network_type <- genMaybe genNetworkType
173 mac_prefix <- genMaybe genName
174 net_family <- arbitrary
175 net <- genIp4NetWithNetmask netmask
176 net6 <- genMaybe genIp6Net
177 gateway <- genMaybe genIp4AddrStr
178 gateway6 <- genMaybe genIp6Addr
179 size <- genMaybe genJSValue
180 res <- liftM Just (genBitString $ netmask2NumHosts netmask)
181 ext_res <- liftM Just (genBitString $ netmask2NumHosts netmask)
182 let n = Network name network_type mac_prefix net_family net net6 gateway
183 gateway6 size res ext_res 0 Set.empty
186 -- | Generates an arbitrary network type.
187 genNetworkType :: Gen NetworkType
188 genNetworkType = elements [ PrivateNetwork, PublicNetwork ]
190 -- | Generate an arbitrary string consisting of '0' and '1' of the given length.
191 genBitString :: Int -> Gen String
192 genBitString len = vectorOf len (elements "01")
194 -- | Generate an arbitrary string consisting of '0' and '1' of the maximum given
196 genBitStringMaxLen :: Int -> Gen String
197 genBitStringMaxLen maxLen = choose (0, maxLen) >>= genBitString
199 -- | Generator for config data with an empty cluster (no instances),
200 -- with N defined nodes.
201 genEmptyCluster :: Int -> Gen ConfigData
202 genEmptyCluster ncount = do
203 nodes <- vector ncount
206 nodes' = zipWith (\n idx ->
207 let newname = nodeName n ++ "-" ++ show idx
208 in (newname, n { nodeGroup = guuid,
209 nodeName = newname}))
211 nodemap = Map.fromList nodes'
212 contnodes = if Map.size nodemap /= ncount
213 then error ("Inconsistent node map, duplicates in" ++
214 " node name list? Names: " ++
215 show (map fst nodes'))
216 else GenericContainer nodemap
217 continsts = GenericContainer Map.empty
219 let contgroups = GenericContainer $ Map.singleton guuid grp
221 cluster <- resize 8 arbitrary
222 let c = ConfigData version cluster contnodes contgroups continsts serial
227 -- | Tests that fillDict behaves correctly
228 prop_fillDict :: [(Int, Int)] -> [(Int, Int)] -> Property
229 prop_fillDict defaults custom =
230 let d_map = Map.fromList defaults
231 d_keys = map fst defaults
232 c_map = Map.fromList custom
233 c_keys = map fst custom
234 in conjoin [ printTestCase "Empty custom filling"
235 (fillDict d_map Map.empty [] == d_map)
236 , printTestCase "Empty defaults filling"
237 (fillDict Map.empty c_map [] == c_map)
238 , printTestCase "Delete all keys"
239 (fillDict d_map c_map (d_keys++c_keys) == Map.empty)
242 -- | Test that the serialisation of 'DiskLogicalId', which is
243 -- implemented manually, is idempotent. Since we don't have a
244 -- standalone JSON instance for DiskLogicalId (it's a data type that
245 -- expands over two fields in a JSObject), we test this by actially
246 -- testing entire Disk serialisations. So this tests two things at
248 prop_Disk_serialisation :: Disk -> Property
249 prop_Disk_serialisation = testSerialisation
251 -- | Check that node serialisation is idempotent.
252 prop_Node_serialisation :: Node -> Property
253 prop_Node_serialisation = testSerialisation
255 -- | Check that instance serialisation is idempotent.
256 prop_Inst_serialisation :: Instance -> Property
257 prop_Inst_serialisation = testSerialisation
259 -- | Check that network serialisation is idempotent.
260 prop_Network_serialisation :: Network -> Property
261 prop_Network_serialisation = testSerialisation
263 -- | Check config serialisation.
264 prop_Config_serialisation :: Property
265 prop_Config_serialisation =
266 forAll (choose (0, maxNodes `div` 4) >>= genEmptyCluster) testSerialisation
268 -- | Custom HUnit test to check the correspondence between Haskell-generated
269 -- networks and their Python decoded, validated and re-encoded version.
270 -- For the technical background of this unit test, check the documentation
271 -- of "case_py_compat_types" of htest/Test/Ganeti/Opcodes.hs
272 case_py_compat_networks :: HUnit.Assertion
273 case_py_compat_networks = do
274 let num_networks = 500::Int
275 sample_networks <- sample' (vectorOf num_networks genValidNetwork)
276 let networks = head sample_networks
277 networks_with_properties = map getNetworkProperties networks
278 serialized = J.encode networks
279 -- check for non-ASCII fields, usually due to 'arbitrary :: String'
280 mapM_ (\net -> when (any (not . isAscii) (J.encode net)) .
281 HUnit.assertFailure $
282 "Network has non-ASCII fields: " ++ show net
285 runPython "from ganeti import network\n\
286 \from ganeti import objects\n\
287 \from ganeti import serializer\n\
289 \net_data = serializer.Load(sys.stdin.read())\n\
290 \decoded = [objects.Network.FromDict(n) for n in net_data]\n\
292 \for net in decoded:\n\
293 \ a = network.AddressPool(net)\n\
294 \ encoded.append((a.GetFreeCount(), a.GetReservedCount(), \\\n\
296 \print serializer.Dump(encoded)" serialized
297 >>= checkPythonResult
298 let deserialised = J.decode py_stdout::J.Result [(Int, Int, Network)]
299 decoded <- case deserialised of
300 J.Ok ops -> return ops
302 HUnit.assertFailure ("Unable to decode networks: " ++ msg)
303 -- this already raised an expection, but we need it
305 >> fail "Unable to decode networks"
306 HUnit.assertEqual "Mismatch in number of returned networks"
307 (length decoded) (length networks_with_properties)
308 mapM_ (uncurry (HUnit.assertEqual "Different result after encoding/decoding")
309 ) $ zip decoded networks_with_properties
311 -- | Creates a tuple of the given network combined with some of its properties
312 -- to be compared against the same properties generated by the python code.
313 getNetworkProperties :: Network -> (Int, Int, Network)
314 getNetworkProperties net =
315 let maybePool = createAddressPool net
317 (Just pool) -> (getFreeCount pool, getReservedCount pool, net)
318 Nothing -> (-1, -1, net)
320 -- | Tests the compatibility between Haskell-serialized node groups and their
321 -- python-decoded and encoded version.
322 case_py_compat_nodegroups :: HUnit.Assertion
323 case_py_compat_nodegroups = do
324 let num_groups = 500::Int
325 sample_groups <- sample' (vectorOf num_groups genNodeGroup)
326 let groups = head sample_groups
327 serialized = J.encode groups
328 -- check for non-ASCII fields, usually due to 'arbitrary :: String'
329 mapM_ (\group -> when (any (not . isAscii) (J.encode group)) .
330 HUnit.assertFailure $
331 "Node group has non-ASCII fields: " ++ show group
334 runPython "from ganeti import objects\n\
335 \from ganeti import serializer\n\
337 \group_data = serializer.Load(sys.stdin.read())\n\
338 \decoded = [objects.NodeGroup.FromDict(g) for g in group_data]\n\
339 \encoded = [g.ToDict() for g in decoded]\n\
340 \print serializer.Dump(encoded)" serialized
341 >>= checkPythonResult
342 let deserialised = J.decode py_stdout::J.Result [NodeGroup]
343 decoded <- case deserialised of
344 J.Ok ops -> return ops
346 HUnit.assertFailure ("Unable to decode node groups: " ++ msg)
347 -- this already raised an expection, but we need it
349 >> fail "Unable to decode node groups"
350 HUnit.assertEqual "Mismatch in number of returned node groups"
351 (length decoded) (length groups)
352 mapM_ (uncurry (HUnit.assertEqual "Different result after encoding/decoding")
353 ) $ zip decoded groups
355 -- | Generates a node group with up to 3 networks.
356 -- | FIXME: This generates still somewhat completely random data, without normal
358 genNodeGroup :: Gen NodeGroup
362 ndparams <- arbitrary
363 alloc_policy <- arbitrary
365 diskparams <- pure (GenericContainer Map.empty)
366 num_networks <- choose (0, 3)
367 net_uuid_list <- vectorOf num_networks (arbitrary::Gen String)
368 nic_param_list <- vectorOf num_networks (arbitrary::Gen PartialNic)
369 net_map <- pure (GenericContainer . Map.fromList $
370 zip net_uuid_list nic_param_list)
376 tags <- Set.fromList <$> genTags
377 let group = NodeGroup name members ndparams alloc_policy ipolicy diskparams
378 net_map ctime mtime uuid serial tags
381 instance Arbitrary NodeGroup where
382 arbitrary = genNodeGroup
386 , 'prop_Disk_serialisation
387 , 'prop_Inst_serialisation
388 , 'prop_Network_serialisation
389 , 'prop_Node_serialisation
390 , 'prop_Config_serialisation
391 , 'case_py_compat_networks
392 , 'case_py_compat_nodegroups