Revision 76a0266e
b/Makefile.am | ||
---|---|---|
481 | 481 |
htools/Ganeti/Jobs.hs \ |
482 | 482 |
htools/Ganeti/Logging.hs \ |
483 | 483 |
htools/Ganeti/Luxi.hs \ |
484 |
htools/Ganeti/Network.hs \ |
|
484 | 485 |
htools/Ganeti/Objects.hs \ |
485 | 486 |
htools/Ganeti/OpCodes.hs \ |
486 | 487 |
htools/Ganeti/OpParams.hs \ |
... | ... | |
523 | 524 |
htest/Test/Ganeti/JSON.hs \ |
524 | 525 |
htest/Test/Ganeti/Jobs.hs \ |
525 | 526 |
htest/Test/Ganeti/Luxi.hs \ |
527 |
htest/Test/Ganeti/Network.hs \ |
|
526 | 528 |
htest/Test/Ganeti/Objects.hs \ |
527 | 529 |
htest/Test/Ganeti/OpCodes.hs \ |
528 | 530 |
htest/Test/Ganeti/Query/Filter.hs \ |
b/htest/Test/Ganeti/Network.hs | ||
---|---|---|
1 |
{-# LANGUAGE TemplateHaskell, TypeSynonymInstances, FlexibleInstances #-} |
|
2 |
{-# OPTIONS_GHC -fno-warn-orphans #-} |
|
3 |
|
|
4 |
module Test.Ganeti.Network |
|
5 |
( testNetwork |
|
6 |
) where |
|
7 |
|
|
8 |
import Test.QuickCheck |
|
9 |
|
|
10 |
import Control.Monad |
|
11 |
|
|
12 |
import Ganeti.Network as Network |
|
13 |
import Ganeti.Objects as Objects |
|
14 |
import Ganeti.Types |
|
15 |
|
|
16 |
import Test.Ganeti.Query.Language (genJSValue) |
|
17 |
import Test.Ganeti.TestHelper |
|
18 |
import Test.Ganeti.TestCommon |
|
19 |
|
|
20 |
import qualified Data.Vector.Unboxed as V |
|
21 |
import qualified Data.Set as S |
|
22 |
|
|
23 |
-- * Generators and arbitrary instances |
|
24 |
|
|
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 |
family <- 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 family 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 |
-- | Generates address pools. The size of the network is intentionally |
|
70 |
-- decoupled from the size of the bit vectors, to avoid slowing down |
|
71 |
-- the tests by generating unnecessary bit strings. |
|
72 |
genAddressPool :: Int -> Gen AddressPool |
|
73 |
genAddressPool maxLenBitVec = do |
|
74 |
net <- arbitrary |
|
75 |
lenBitVec <- choose (0, maxLenBitVec) |
|
76 |
res <- genBitVector lenBitVec |
|
77 |
ext_res <- genBitVector lenBitVec |
|
78 |
return AddressPool { network = net |
|
79 |
, reservations = res |
|
80 |
, extReservations = ext_res } |
|
81 |
|
|
82 |
instance Arbitrary AddressPool where |
|
83 |
arbitrary = genAddressPool ((2::Int)^(8::Int)) |
|
84 |
|
|
85 |
-- * Test cases |
|
86 |
|
|
87 |
-- | Check the mapping of bit strings to bit vectors |
|
88 |
prop_bitStringToBitVector :: Property |
|
89 |
prop_bitStringToBitVector = |
|
90 |
forAll (genBitStringMaxLen 256) $ \bs -> |
|
91 |
let bitList = V.toList $ Network.bitStringToBitVector bs |
|
92 |
bitCharList = Prelude.zip bitList bs |
|
93 |
in Prelude.all checkBit bitCharList |
|
94 |
|
|
95 |
-- | Check whether an element of a bit vector is consistent with an element |
|
96 |
-- of a bit string (containing '0' and '1' characters). |
|
97 |
checkBit :: (Bool, Char) -> Bool |
|
98 |
checkBit (False, '0') = True |
|
99 |
checkBit (True, '1') = True |
|
100 |
checkBit _ = False |
|
101 |
|
|
102 |
-- | Check creation of an address pool when a network is given. |
|
103 |
prop_createAddressPool :: Objects.Network -> Property |
|
104 |
prop_createAddressPool n = |
|
105 |
let valid = networkIsValid n |
|
106 |
in case createAddressPool n of |
|
107 |
Just _ -> True ==? valid |
|
108 |
Nothing -> False ==? valid |
|
109 |
|
|
110 |
-- | Check that the address pool's properties are calculated correctly. |
|
111 |
prop_addressPoolProperties :: AddressPool -> Property |
|
112 |
prop_addressPoolProperties a = |
|
113 |
conjoin |
|
114 |
[ printTestCase |
|
115 |
("Not all reservations are included in 'allReservations' of " ++ |
|
116 |
"address pool:" ++ show a) (allReservationsSubsumesInternal a) |
|
117 |
, printTestCase |
|
118 |
("Not all external reservations are covered by 'allReservations' " ++ |
|
119 |
"of address pool: " ++ show a) |
|
120 |
(allReservationsSubsumesExternal a) |
|
121 |
, printTestCase |
|
122 |
("The counts of free and reserved addresses do not add up for " ++ |
|
123 |
"address pool: " ++ show a) |
|
124 |
(checkCounts a) |
|
125 |
, printTestCase |
|
126 |
("'isFull' wrongly classified the status of the address pool: " ++ |
|
127 |
show a) (checkIsFull a) |
|
128 |
, printTestCase |
|
129 |
("Network map is inconsistent with reservations of address pool: " ++ |
|
130 |
show a) (checkGetMap a) |
|
131 |
] |
|
132 |
|
|
133 |
-- | Check that all internally reserved ips are included in 'allReservations'. |
|
134 |
allReservationsSubsumesInternal :: AddressPool -> Bool |
|
135 |
allReservationsSubsumesInternal a = |
|
136 |
bitVectorSubsumes (allReservations a) (reservations a) |
|
137 |
|
|
138 |
-- | Check that all externally reserved ips are included in 'allReservations'. |
|
139 |
allReservationsSubsumesExternal :: AddressPool -> Bool |
|
140 |
allReservationsSubsumesExternal a = |
|
141 |
bitVectorSubsumes (allReservations a) (extReservations a) |
|
142 |
|
|
143 |
-- | Checks if one bit vector subsumes the other one. |
|
144 |
bitVectorSubsumes :: V.Vector Bool -> V.Vector Bool -> Bool |
|
145 |
bitVectorSubsumes v1 v2 = V.and $ |
|
146 |
V.zipWith (\a b -> not b || a) v1 v2 |
|
147 |
|
|
148 |
-- | Check that the counts of free and reserved ips add up. |
|
149 |
checkCounts :: AddressPool -> Bool |
|
150 |
checkCounts a = |
|
151 |
let res = reservations a |
|
152 |
in V.length res == getFreeCount a + getReservedCount a |
|
153 |
|
|
154 |
-- | Check that the detection of a full network works correctly. |
|
155 |
checkIsFull :: AddressPool -> Bool |
|
156 |
checkIsFull a = isFull a == V.notElem False (allReservations a) |
|
157 |
|
|
158 |
-- | Check that the map representation of the network corresponds to the |
|
159 |
-- network's reservations. |
|
160 |
checkGetMap :: AddressPool -> Bool |
|
161 |
checkGetMap a = |
|
162 |
allReservations a == V.fromList (Prelude.map (== 'X') (getMap a)) |
|
163 |
|
|
164 |
testSuite "Network" |
|
165 |
[ 'prop_bitStringToBitVector |
|
166 |
, 'prop_createAddressPool |
|
167 |
, 'prop_addressPoolProperties |
|
168 |
] |
|
169 |
|
b/htest/Test/Ganeti/Objects.hs | ||
---|---|---|
38 | 38 |
import qualified Data.Map as Map |
39 | 39 |
import qualified Data.Set as Set |
40 | 40 |
|
41 |
import Test.Ganeti.Query.Language (genJSValue) |
|
41 | 42 |
import Test.Ganeti.TestHelper |
42 | 43 |
import Test.Ganeti.TestCommon |
43 | 44 |
import Test.Ganeti.Types () |
... | ... | |
162 | 163 |
|
163 | 164 |
$(genArbitrary ''Cluster) |
164 | 165 |
|
166 |
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) |
|
194 |
|
|
165 | 195 |
-- | Generator for config data with an empty cluster (no instances), |
166 | 196 |
-- with N defined nodes. |
167 | 197 |
genEmptyCluster :: Int -> Gen ConfigData |
... | ... | |
222 | 252 |
prop_Inst_serialisation :: Instance -> Property |
223 | 253 |
prop_Inst_serialisation = testSerialisation |
224 | 254 |
|
255 |
-- | Check that network serialisation is idempotent. |
|
256 |
prop_Network_serialisation :: Network -> Property |
|
257 |
prop_Network_serialisation = testSerialisation |
|
258 |
|
|
225 | 259 |
-- | Check config serialisation. |
226 | 260 |
prop_Config_serialisation :: Property |
227 | 261 |
prop_Config_serialisation = |
... | ... | |
231 | 265 |
[ 'prop_fillDict |
232 | 266 |
, 'prop_Disk_serialisation |
233 | 267 |
, 'prop_Inst_serialisation |
268 |
, 'prop_Network_serialisation |
|
234 | 269 |
, 'prop_Node_serialisation |
235 | 270 |
, 'prop_Config_serialisation |
236 | 271 |
] |
b/htest/Test/Ganeti/Query/Language.hs | ||
---|---|---|
29 | 29 |
module Test.Ganeti.Query.Language |
30 | 30 |
( testQuery_Language |
31 | 31 |
, genFilter |
32 |
, genJSValue |
|
32 | 33 |
) where |
33 | 34 |
|
34 | 35 |
import Test.QuickCheck |
b/htest/test.hs | ||
---|---|---|
52 | 52 |
import Test.Ganeti.JSON |
53 | 53 |
import Test.Ganeti.Jobs |
54 | 54 |
import Test.Ganeti.Luxi |
55 |
import Test.Ganeti.Network |
|
55 | 56 |
import Test.Ganeti.Objects |
56 | 57 |
import Test.Ganeti.OpCodes |
57 | 58 |
import Test.Ganeti.Query.Filter |
... | ... | |
100 | 101 |
, testJSON |
101 | 102 |
, testJobs |
102 | 103 |
, testLuxi |
104 |
, testNetwork |
|
103 | 105 |
, testObjects |
104 | 106 |
, testOpCodes |
105 | 107 |
, testQuery_Filter |
b/htools/Ganeti/Network.hs | ||
---|---|---|
1 |
{-| Implementation of the Ganeti network objects. |
|
2 |
|
|
3 |
This is does not (yet) cover all methods that are provided in the |
|
4 |
corresponding python implementation (network.py). |
|
5 |
|
|
6 |
-} |
|
7 |
|
|
8 |
{- |
|
9 |
|
|
10 |
Copyright (C) 2011, 2012 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 Ganeti.Network |
|
30 |
( AddressPool(..) |
|
31 |
, createAddressPool |
|
32 |
, bitStringToBitVector |
|
33 |
, allReservations |
|
34 |
, getReservedCount |
|
35 |
, getFreeCount |
|
36 |
, isFull |
|
37 |
, getMap |
|
38 |
, networkIsValid |
|
39 |
) where |
|
40 |
|
|
41 |
import qualified Data.Vector.Unboxed as V |
|
42 |
|
|
43 |
import Ganeti.Objects |
|
44 |
|
|
45 |
data AddressPool = AddressPool { network :: Network, |
|
46 |
reservations :: V.Vector Bool, |
|
47 |
extReservations :: V.Vector Bool } |
|
48 |
deriving (Show) |
|
49 |
|
|
50 |
-- | Create an address pool from a network. |
|
51 |
createAddressPool :: Network -> Maybe AddressPool |
|
52 |
createAddressPool n |
|
53 |
| networkIsValid n = |
|
54 |
let res = maybeStr2BitVec $ networkReservations n |
|
55 |
ext_res = maybeStr2BitVec $ networkExtReservations n |
|
56 |
in Just AddressPool { reservations = res |
|
57 |
, extReservations = ext_res |
|
58 |
, network = n } |
|
59 |
| otherwise = Nothing |
|
60 |
|
|
61 |
-- | Checks the consistency of the network object. So far, only checks the |
|
62 |
-- length of the reservation strings. |
|
63 |
networkIsValid :: Network -> Bool |
|
64 |
networkIsValid n = sameLength (networkReservations n) (networkExtReservations n) |
|
65 |
|
|
66 |
-- | Checks if two maybe strings are both nothing or of equal length. |
|
67 |
sameLength :: Maybe String -> Maybe String -> Bool |
|
68 |
sameLength Nothing Nothing = True |
|
69 |
sameLength (Just s1) (Just s2) = length s1 == length s2 |
|
70 |
sameLength _ _ = False |
|
71 |
|
|
72 |
-- | Converts a maybe bit string to a bit vector. Returns an empty bit vector on |
|
73 |
-- nothing. |
|
74 |
maybeStr2BitVec :: Maybe String -> V.Vector Bool |
|
75 |
maybeStr2BitVec (Just s) = bitStringToBitVector s |
|
76 |
maybeStr2BitVec Nothing = V.fromList ([]::[Bool]) |
|
77 |
|
|
78 |
-- | Converts a string to a bit vector. The character '0' is interpreted |
|
79 |
-- as 'False', all others as 'True'. |
|
80 |
bitStringToBitVector :: String -> V.Vector Bool |
|
81 |
bitStringToBitVector = V.fromList . map (/= '0') |
|
82 |
|
|
83 |
-- | Get a bit vector of all reservations (internal and external) combined. |
|
84 |
allReservations :: AddressPool -> V.Vector Bool |
|
85 |
allReservations a = V.zipWith (||) (reservations a) (extReservations a) |
|
86 |
|
|
87 |
-- | Get the count of reserved addresses. |
|
88 |
getReservedCount :: AddressPool -> Int |
|
89 |
getReservedCount = V.length . V.filter (== True) . allReservations |
|
90 |
|
|
91 |
-- | Get the count of free addresses. |
|
92 |
getFreeCount :: AddressPool -> Int |
|
93 |
getFreeCount = V.length . V.filter (== False) . allReservations |
|
94 |
|
|
95 |
-- | Check whether the network is full. |
|
96 |
isFull :: AddressPool -> Bool |
|
97 |
isFull = V.and . allReservations |
|
98 |
|
|
99 |
-- | Return a textual representation of the network's occupation status. |
|
100 |
getMap :: AddressPool -> String |
|
101 |
getMap = V.toList . V.map mapPixel . allReservations |
|
102 |
where mapPixel c = if c then 'X' else '.' |
|
103 |
|
b/htools/Ganeti/Objects.hs | ||
---|---|---|
87 | 87 |
, TagsObject(..) |
88 | 88 |
, DictObject(..) -- re-exported from THH |
89 | 89 |
, TagSet -- re-exported from THH |
90 |
, Network(..) |
|
90 | 91 |
) where |
91 | 92 |
|
92 | 93 |
import Data.List (foldl') |
... | ... | |
590 | 591 |
|
591 | 592 |
instance SerialNoObject ConfigData where |
592 | 593 |
serialOf = configSerial |
594 |
|
|
595 |
-- * Network definitions |
|
596 |
|
|
597 |
-- FIXME: Not all types might be correct here, since they |
|
598 |
-- haven't been exhaustively deduced from the python code yet. |
|
599 |
$(buildObject "Network" "network" $ |
|
600 |
[ simpleField "name" [t| NonEmptyString |] |
|
601 |
, optionalField $ |
|
602 |
simpleField "network_type" [t| NetworkType |] |
|
603 |
, optionalField $ |
|
604 |
simpleField "mac_prefix" [t| String |] |
|
605 |
, optionalField $ |
|
606 |
simpleField "family" [t| Int |] |
|
607 |
, simpleField "network" [t| NonEmptyString |] |
|
608 |
, optionalField $ |
|
609 |
simpleField "network6" [t| String |] |
|
610 |
, optionalField $ |
|
611 |
simpleField "gateway" [t| String |] |
|
612 |
, optionalField $ |
|
613 |
simpleField "gateway6" [t| String |] |
|
614 |
, optionalField $ |
|
615 |
simpleField "size" [t| J.JSValue |] |
|
616 |
, optionalField $ |
|
617 |
simpleField "reservations" [t| String |] |
|
618 |
, optionalField $ |
|
619 |
simpleField "ext_reservations" [t| String |] |
|
620 |
] |
|
621 |
++ serialFields |
|
622 |
++ tagsFields) |
|
623 |
|
|
624 |
instance SerialNoObject Network where |
|
625 |
serialOf = networkSerial |
|
626 |
|
|
627 |
instance TagsObject Network where |
|
628 |
tagsOf = networkTags |
|
629 |
|
Also available in: Unified diff