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)
183 let n = Network name network_type mac_prefix net_family net net6 gateway
184 gateway6 size res ext_res uuid 0 Set.empty
187 -- | Generates an arbitrary network type.
188 genNetworkType :: Gen NetworkType
189 genNetworkType = elements [ PrivateNetwork, PublicNetwork ]
191 -- | Generate an arbitrary string consisting of '0' and '1' of the given length.
192 genBitString :: Int -> Gen String
193 genBitString len = vectorOf len (elements "01")
195 -- | Generate an arbitrary string consisting of '0' and '1' of the maximum given
197 genBitStringMaxLen :: Int -> Gen String
198 genBitStringMaxLen maxLen = choose (0, maxLen) >>= genBitString
200 -- | Generator for config data with an empty cluster (no instances),
201 -- with N defined nodes.
202 genEmptyCluster :: Int -> Gen ConfigData
203 genEmptyCluster ncount = do
204 nodes <- vector ncount
207 nodes' = zipWith (\n idx ->
208 let newname = nodeName n ++ "-" ++ show idx
209 in (newname, n { nodeGroup = guuid,
210 nodeName = newname}))
212 nodemap = Map.fromList nodes'
213 contnodes = if Map.size nodemap /= ncount
214 then error ("Inconsistent node map, duplicates in" ++
215 " node name list? Names: " ++
216 show (map fst nodes'))
217 else GenericContainer nodemap
218 continsts = GenericContainer Map.empty
219 networks = GenericContainer Map.empty
221 let contgroups = GenericContainer $ Map.singleton guuid grp
223 cluster <- resize 8 arbitrary
224 let c = ConfigData version cluster contnodes contgroups continsts networks
230 -- | Tests that fillDict behaves correctly
231 prop_fillDict :: [(Int, Int)] -> [(Int, Int)] -> Property
232 prop_fillDict defaults custom =
233 let d_map = Map.fromList defaults
234 d_keys = map fst defaults
235 c_map = Map.fromList custom
236 c_keys = map fst custom
237 in conjoin [ printTestCase "Empty custom filling"
238 (fillDict d_map Map.empty [] == d_map)
239 , printTestCase "Empty defaults filling"
240 (fillDict Map.empty c_map [] == c_map)
241 , printTestCase "Delete all keys"
242 (fillDict d_map c_map (d_keys++c_keys) == Map.empty)
245 -- | Test that the serialisation of 'DiskLogicalId', which is
246 -- implemented manually, is idempotent. Since we don't have a
247 -- standalone JSON instance for DiskLogicalId (it's a data type that
248 -- expands over two fields in a JSObject), we test this by actially
249 -- testing entire Disk serialisations. So this tests two things at
251 prop_Disk_serialisation :: Disk -> Property
252 prop_Disk_serialisation = testSerialisation
254 -- | Check that node serialisation is idempotent.
255 prop_Node_serialisation :: Node -> Property
256 prop_Node_serialisation = testSerialisation
258 -- | Check that instance serialisation is idempotent.
259 prop_Inst_serialisation :: Instance -> Property
260 prop_Inst_serialisation = testSerialisation
262 -- | Check that network serialisation is idempotent.
263 prop_Network_serialisation :: Network -> Property
264 prop_Network_serialisation = testSerialisation
266 -- | Check config serialisation.
267 prop_Config_serialisation :: Property
268 prop_Config_serialisation =
269 forAll (choose (0, maxNodes `div` 4) >>= genEmptyCluster) testSerialisation
271 -- | Custom HUnit test to check the correspondence between Haskell-generated
272 -- networks and their Python decoded, validated and re-encoded version.
273 -- For the technical background of this unit test, check the documentation
274 -- of "case_py_compat_types" of test/hs/Test/Ganeti/Opcodes.hs
275 case_py_compat_networks :: HUnit.Assertion
276 case_py_compat_networks = do
277 let num_networks = 500::Int
278 sample_networks <- sample' (vectorOf num_networks genValidNetwork)
279 let networks = head sample_networks
280 networks_with_properties = map getNetworkProperties networks
281 serialized = J.encode networks
282 -- check for non-ASCII fields, usually due to 'arbitrary :: String'
283 mapM_ (\net -> when (any (not . isAscii) (J.encode net)) .
284 HUnit.assertFailure $
285 "Network has non-ASCII fields: " ++ show net
288 runPython "from ganeti import network\n\
289 \from ganeti import objects\n\
290 \from ganeti import serializer\n\
292 \net_data = serializer.Load(sys.stdin.read())\n\
293 \decoded = [objects.Network.FromDict(n) for n in net_data]\n\
295 \for net in decoded:\n\
296 \ a = network.AddressPool(net)\n\
297 \ encoded.append((a.GetFreeCount(), a.GetReservedCount(), \\\n\
299 \print serializer.Dump(encoded)" serialized
300 >>= checkPythonResult
301 let deserialised = J.decode py_stdout::J.Result [(Int, Int, Network)]
302 decoded <- case deserialised of
303 J.Ok ops -> return ops
305 HUnit.assertFailure ("Unable to decode networks: " ++ msg)
306 -- this already raised an expection, but we need it
308 >> fail "Unable to decode networks"
309 HUnit.assertEqual "Mismatch in number of returned networks"
310 (length decoded) (length networks_with_properties)
311 mapM_ (uncurry (HUnit.assertEqual "Different result after encoding/decoding")
312 ) $ zip decoded networks_with_properties
314 -- | Creates a tuple of the given network combined with some of its properties
315 -- to be compared against the same properties generated by the python code.
316 getNetworkProperties :: Network -> (Int, Int, Network)
317 getNetworkProperties net =
318 let maybePool = createAddressPool net
320 (Just pool) -> (getFreeCount pool, getReservedCount pool, net)
321 Nothing -> (-1, -1, net)
323 -- | Tests the compatibility between Haskell-serialized node groups and their
324 -- python-decoded and encoded version.
325 case_py_compat_nodegroups :: HUnit.Assertion
326 case_py_compat_nodegroups = do
327 let num_groups = 500::Int
328 sample_groups <- sample' (vectorOf num_groups genNodeGroup)
329 let groups = head sample_groups
330 serialized = J.encode groups
331 -- check for non-ASCII fields, usually due to 'arbitrary :: String'
332 mapM_ (\group -> when (any (not . isAscii) (J.encode group)) .
333 HUnit.assertFailure $
334 "Node group has non-ASCII fields: " ++ show group
337 runPython "from ganeti import objects\n\
338 \from ganeti import serializer\n\
340 \group_data = serializer.Load(sys.stdin.read())\n\
341 \decoded = [objects.NodeGroup.FromDict(g) for g in group_data]\n\
342 \encoded = [g.ToDict() for g in decoded]\n\
343 \print serializer.Dump(encoded)" serialized
344 >>= checkPythonResult
345 let deserialised = J.decode py_stdout::J.Result [NodeGroup]
346 decoded <- case deserialised of
347 J.Ok ops -> return ops
349 HUnit.assertFailure ("Unable to decode node groups: " ++ msg)
350 -- this already raised an expection, but we need it
352 >> fail "Unable to decode node groups"
353 HUnit.assertEqual "Mismatch in number of returned node groups"
354 (length decoded) (length groups)
355 mapM_ (uncurry (HUnit.assertEqual "Different result after encoding/decoding")
356 ) $ zip decoded groups
358 -- | Generates a node group with up to 3 networks.
359 -- | FIXME: This generates still somewhat completely random data, without normal
361 genNodeGroup :: Gen NodeGroup
365 ndparams <- arbitrary
366 alloc_policy <- arbitrary
368 diskparams <- pure (GenericContainer Map.empty)
369 num_networks <- choose (0, 3)
370 net_uuid_list <- vectorOf num_networks (arbitrary::Gen String)
371 nic_param_list <- vectorOf num_networks (arbitrary::Gen PartialNicParams)
372 net_map <- pure (GenericContainer . Map.fromList $
373 zip net_uuid_list nic_param_list)
379 tags <- Set.fromList <$> genTags
380 let group = NodeGroup name members ndparams alloc_policy ipolicy diskparams
381 net_map ctime mtime uuid serial tags
384 instance Arbitrary NodeGroup where
385 arbitrary = genNodeGroup
389 , 'prop_Disk_serialisation
390 , 'prop_Inst_serialisation
391 , 'prop_Network_serialisation
392 , 'prop_Node_serialisation
393 , 'prop_Config_serialisation
394 , 'case_py_compat_networks
395 , 'case_py_compat_nodegroups