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