1 {-# LANGUAGE TemplateHaskell #-}
3 {-| Implementation of the Ganeti config objects.
5 Some object fields are not implemented yet, and as such they are
12 Copyright (C) 2011, 2012, 2013 Google Inc.
14 This program is free software; you can redistribute it and/or modify
15 it under the terms of the GNU General Public License as published by
16 the Free Software Foundation; either version 2 of the License, or
17 (at your option) any later version.
19 This program is distributed in the hope that it will be useful, but
20 WITHOUT ANY WARRANTY; without even the implied warranty of
21 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
22 General Public License for more details.
24 You should have received a copy of the GNU General Public License
25 along with this program; if not, write to the Free Software
26 Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
36 , PartialNicParams(..)
65 , FilledISpecParams(..)
66 , PartialISpecParams(..)
89 , DictObject(..) -- re-exported from THH
90 , TagSet -- re-exported from THH
98 import Control.Applicative
99 import Data.List (foldl')
101 import qualified Data.Map as Map
102 import qualified Data.Set as Set
104 import Text.JSON (showJSON, readJSON, JSON, JSValue(..), fromJSString)
105 import qualified Text.JSON as J
107 import qualified Ganeti.Constants as C
111 import Ganeti.Utils (sepSplit, tryRead)
113 -- * Generic definitions
115 -- | Fills one map with keys from the other map, if not already
116 -- existing. Mirrors objects.py:FillDict.
117 fillDict :: (Ord k) => Map.Map k v -> Map.Map k v -> [k] -> Map.Map k v
118 fillDict defaults custom skip_keys =
119 let updated = Map.union custom defaults
120 in foldl' (flip Map.delete) updated skip_keys
122 -- | The VTYPES, a mini-type system in Python.
123 $(declareSADT "VType"
124 [ ("VTypeString", 'C.vtypeString)
125 , ("VTypeMaybeString", 'C.vtypeMaybeString)
126 , ("VTypeBool", 'C.vtypeBool)
127 , ("VTypeSize", 'C.vtypeSize)
128 , ("VTypeInt", 'C.vtypeInt)
130 $(makeJSONInstance ''VType)
132 -- | The hypervisor parameter type. This is currently a simple map,
133 -- without type checking on key/value pairs.
134 type HvParams = Container JSValue
136 -- | The OS parameters type. This is, and will remain, a string
137 -- container, since the keys are dynamically declared by the OSes, and
138 -- the values are always strings.
139 type OsParams = Container String
141 -- | Class of objects that have timestamps.
142 class TimeStampObject a where
143 cTimeOf :: a -> Double
144 mTimeOf :: a -> Double
146 -- | Class of objects that have an UUID.
147 class UuidObject a where
148 uuidOf :: a -> String
150 -- | Class of object that have a serial number.
151 class SerialNoObject a where
154 -- | Class of objects that have tags.
155 class TagsObject a where
156 tagsOf :: a -> Set.Set String
158 -- * Node role object
160 $(declareSADT "NodeRole"
161 [ ("NROffline", 'C.nrOffline)
162 , ("NRDrained", 'C.nrDrained)
163 , ("NRRegular", 'C.nrRegular)
164 , ("NRCandidate", 'C.nrMcandidate)
165 , ("NRMaster", 'C.nrMaster)
167 $(makeJSONInstance ''NodeRole)
169 -- | The description of the node role.
170 roleDescription :: NodeRole -> String
171 roleDescription NROffline = "offline"
172 roleDescription NRDrained = "drained"
173 roleDescription NRRegular = "regular"
174 roleDescription NRCandidate = "master candidate"
175 roleDescription NRMaster = "master"
177 -- * Network definitions
181 -- | Custom type for a simple IPv4 address.
182 data Ip4Address = Ip4Address Word8 Word8 Word8 Word8
185 instance Show Ip4Address where
186 show (Ip4Address a b c d) = show a ++ "." ++ show b ++ "." ++
187 show c ++ "." ++ show d
189 -- | Parses an IPv4 address from a string.
190 readIp4Address :: (Applicative m, Monad m) => String -> m Ip4Address
192 case sepSplit '.' s of
193 [a, b, c, d] -> Ip4Address <$>
194 tryRead "first octect" a <*>
195 tryRead "second octet" b <*>
196 tryRead "third octet" c <*>
197 tryRead "fourth octet" d
198 _ -> fail $ "Can't parse IPv4 address from string " ++ s
200 -- | JSON instance for 'Ip4Address'.
201 instance JSON Ip4Address where
202 showJSON = showJSON . show
203 readJSON (JSString s) = readIp4Address (fromJSString s)
204 readJSON v = fail $ "Invalid JSON value " ++ show v ++ " for an IPv4 address"
206 -- | \"Next\" address implementation for IPv4 addresses.
208 -- Note that this loops! Note also that this is a very dumb
210 nextIp4Address :: Ip4Address -> Ip4Address
211 nextIp4Address (Ip4Address a b c d) =
212 let inc xs y = if all (==0) xs then y + 1 else y
216 a' = inc [b', c', d'] a
217 in Ip4Address a' b' c' d'
219 -- | Custom type for an IPv4 network.
220 data Ip4Network = Ip4Network Ip4Address Word8
223 instance Show Ip4Network where
224 show (Ip4Network ip netmask) = show ip ++ "/" ++ show netmask
226 -- | JSON instance for 'Ip4Network'.
227 instance JSON Ip4Network where
228 showJSON = showJSON . show
229 readJSON (JSString s) =
230 case sepSplit '/' (fromJSString s) of
232 ip' <- readIp4Address ip
233 nm' <- tryRead "parsing netmask" nm
234 if nm' >= 0 && nm' <= 32
235 then return $ Ip4Network ip' nm'
236 else fail $ "Invalid netmask " ++ show nm' ++ " from string " ++
238 _ -> fail $ "Can't parse IPv4 network from string " ++ fromJSString s
239 readJSON v = fail $ "Invalid JSON value " ++ show v ++ " for an IPv4 network"
241 -- ** Ganeti \"network\" config object.
243 -- FIXME: Not all types might be correct here, since they
244 -- haven't been exhaustively deduced from the python code yet.
245 $(buildObject "Network" "network" $
246 [ simpleField "name" [t| NonEmptyString |]
248 simpleField "mac_prefix" [t| String |]
249 , simpleField "network" [t| Ip4Network |]
251 simpleField "network6" [t| String |]
253 simpleField "gateway" [t| Ip4Address |]
255 simpleField "gateway6" [t| String |]
257 simpleField "reservations" [t| String |]
259 simpleField "ext_reservations" [t| String |]
265 instance SerialNoObject Network where
266 serialOf = networkSerial
268 instance TagsObject Network where
271 instance UuidObject Network where
276 $(buildParam "Nic" "nicp"
277 [ simpleField "mode" [t| NICMode |]
278 , simpleField "link" [t| String |]
281 $(buildObject "PartialNic" "nic" $
282 [ simpleField "mac" [t| String |]
283 , optionalField $ simpleField "ip" [t| String |]
284 , simpleField "nicparams" [t| PartialNicParams |]
285 , optionalField $ simpleField "network" [t| String |]
286 , optionalField $ simpleField "name" [t| String |]
289 instance UuidObject PartialNic where
292 -- * Disk definitions
294 $(declareSADT "DiskMode"
295 [ ("DiskRdOnly", 'C.diskRdonly)
296 , ("DiskRdWr", 'C.diskRdwr)
298 $(makeJSONInstance ''DiskMode)
300 $(declareSADT "DiskType"
302 , ("LD_DRBD8", 'C.ldDrbd8)
303 , ("LD_FILE", 'C.ldFile)
304 , ("LD_BLOCKDEV", 'C.ldBlockdev)
305 , ("LD_RADOS", 'C.ldRbd)
306 , ("LD_EXT", 'C.ldExt)
308 $(makeJSONInstance ''DiskType)
310 -- | The persistent block driver type. Currently only one type is allowed.
311 $(declareSADT "BlockDriver"
312 [ ("BlockDrvManual", 'C.blockdevDriverManual)
314 $(makeJSONInstance ''BlockDriver)
316 -- | Constant for the dev_type key entry in the disk config.
320 -- | The disk configuration type. This includes the disk type itself,
321 -- for a more complete consistency. Note that since in the Python
322 -- code-base there's no authoritative place where we document the
323 -- logical id, this is probably a good reference point.
325 = LIDPlain String String -- ^ Volume group, logical volume
326 | LIDDrbd8 String String Int Int Int String
327 -- ^ NodeA, NodeB, Port, MinorA, MinorB, Secret
328 | LIDFile FileDriver String -- ^ Driver, path
329 | LIDBlockDev BlockDriver String -- ^ Driver, path (must be under /dev)
330 | LIDRados String String -- ^ Unused, path
331 | LIDExt String String -- ^ ExtProvider, unique name
334 -- | Mapping from a logical id to a disk type.
335 lidDiskType :: DiskLogicalId -> DiskType
336 lidDiskType (LIDPlain {}) = LD_LV
337 lidDiskType (LIDDrbd8 {}) = LD_DRBD8
338 lidDiskType (LIDFile {}) = LD_FILE
339 lidDiskType (LIDBlockDev {}) = LD_BLOCKDEV
340 lidDiskType (LIDRados {}) = LD_RADOS
341 lidDiskType (LIDExt {}) = LD_EXT
343 -- | Builds the extra disk_type field for a given logical id.
344 lidEncodeType :: DiskLogicalId -> [(String, JSValue)]
345 lidEncodeType v = [(devType, showJSON . lidDiskType $ v)]
347 -- | Custom encoder for DiskLogicalId (logical id only).
348 encodeDLId :: DiskLogicalId -> JSValue
349 encodeDLId (LIDPlain vg lv) = JSArray [showJSON vg, showJSON lv]
350 encodeDLId (LIDDrbd8 nodeA nodeB port minorA minorB key) =
351 JSArray [ showJSON nodeA, showJSON nodeB, showJSON port
352 , showJSON minorA, showJSON minorB, showJSON key ]
353 encodeDLId (LIDRados pool name) = JSArray [showJSON pool, showJSON name]
354 encodeDLId (LIDFile driver name) = JSArray [showJSON driver, showJSON name]
355 encodeDLId (LIDBlockDev driver name) = JSArray [showJSON driver, showJSON name]
356 encodeDLId (LIDExt extprovider name) =
357 JSArray [showJSON extprovider, showJSON name]
359 -- | Custom encoder for DiskLogicalId, composing both the logical id
360 -- and the extra disk_type field.
361 encodeFullDLId :: DiskLogicalId -> (JSValue, [(String, JSValue)])
362 encodeFullDLId v = (encodeDLId v, lidEncodeType v)
364 -- | Custom decoder for DiskLogicalId. This is manual for now, since
365 -- we don't have yet automation for separate-key style fields.
366 decodeDLId :: [(String, JSValue)] -> JSValue -> J.Result DiskLogicalId
367 decodeDLId obj lid = do
368 dtype <- fromObj obj devType
372 JSArray [nA, nB, p, mA, mB, k] -> do
379 return $ LIDDrbd8 nA' nB' p' mA' mB' k'
380 _ -> fail "Can't read logical_id for DRBD8 type"
383 JSArray [vg, lv] -> do
386 return $ LIDPlain vg' lv'
387 _ -> fail "Can't read logical_id for plain type"
390 JSArray [driver, path] -> do
391 driver' <- readJSON driver
392 path' <- readJSON path
393 return $ LIDFile driver' path'
394 _ -> fail "Can't read logical_id for file type"
397 JSArray [driver, path] -> do
398 driver' <- readJSON driver
399 path' <- readJSON path
400 return $ LIDBlockDev driver' path'
401 _ -> fail "Can't read logical_id for blockdev type"
404 JSArray [driver, path] -> do
405 driver' <- readJSON driver
406 path' <- readJSON path
407 return $ LIDRados driver' path'
408 _ -> fail "Can't read logical_id for rdb type"
411 JSArray [extprovider, name] -> do
412 extprovider' <- readJSON extprovider
413 name' <- readJSON name
414 return $ LIDExt extprovider' name'
415 _ -> fail "Can't read logical_id for extstorage type"
417 -- | Disk data structure.
419 -- This is declared manually as it's a recursive structure, and our TH
420 -- code currently can't build it.
422 { diskLogicalId :: DiskLogicalId
423 -- , diskPhysicalId :: String
424 , diskChildren :: [Disk]
425 , diskIvName :: String
427 , diskMode :: DiskMode
428 , diskName :: Maybe String
429 , diskSpindles :: Maybe Int
431 } deriving (Show, Eq)
433 $(buildObjectSerialisation "Disk" $
434 [ customField 'decodeDLId 'encodeFullDLId ["dev_type"] $
435 simpleField "logical_id" [t| DiskLogicalId |]
436 -- , simpleField "physical_id" [t| String |]
437 , defaultField [| [] |] $ simpleField "children" [t| [Disk] |]
438 , defaultField [| "" |] $ simpleField "iv_name" [t| String |]
439 , simpleField "size" [t| Int |]
440 , defaultField [| DiskRdWr |] $ simpleField "mode" [t| DiskMode |]
441 , optionalField $ simpleField "name" [t| String |]
442 , optionalField $ simpleField "spindles" [t| Int |]
446 instance UuidObject Disk where
449 -- * Instance definitions
451 $(declareSADT "AdminState"
452 [ ("AdminOffline", 'C.adminstOffline)
453 , ("AdminDown", 'C.adminstDown)
454 , ("AdminUp", 'C.adminstUp)
456 $(makeJSONInstance ''AdminState)
458 $(buildParam "Be" "bep"
459 [ simpleField "minmem" [t| Int |]
460 , simpleField "maxmem" [t| Int |]
461 , simpleField "vcpus" [t| Int |]
462 , simpleField "auto_balance" [t| Bool |]
465 $(buildObject "Instance" "inst" $
466 [ simpleField "name" [t| String |]
467 , simpleField "primary_node" [t| String |]
468 , simpleField "os" [t| String |]
469 , simpleField "hypervisor" [t| Hypervisor |]
470 , simpleField "hvparams" [t| HvParams |]
471 , simpleField "beparams" [t| PartialBeParams |]
472 , simpleField "osparams" [t| OsParams |]
473 , simpleField "admin_state" [t| AdminState |]
474 , simpleField "nics" [t| [PartialNic] |]
475 , simpleField "disks" [t| [Disk] |]
476 , simpleField "disk_template" [t| DiskTemplate |]
477 , simpleField "disks_active" [t| Bool |]
478 , optionalField $ simpleField "network_port" [t| Int |]
485 instance TimeStampObject Instance where
489 instance UuidObject Instance where
492 instance SerialNoObject Instance where
493 serialOf = instSerial
495 instance TagsObject Instance where
498 -- * IPolicy definitions
500 $(buildParam "ISpec" "ispec"
501 [ simpleField C.ispecMemSize [t| Int |]
502 , simpleField C.ispecDiskSize [t| Int |]
503 , simpleField C.ispecDiskCount [t| Int |]
504 , simpleField C.ispecCpuCount [t| Int |]
505 , simpleField C.ispecNicCount [t| Int |]
506 , simpleField C.ispecSpindleUse [t| Int |]
509 $(buildObject "MinMaxISpecs" "mmis"
510 [ renameField "MinSpec" $ simpleField "min" [t| FilledISpecParams |]
511 , renameField "MaxSpec" $ simpleField "max" [t| FilledISpecParams |]
514 -- | Custom partial ipolicy. This is not built via buildParam since it
515 -- has a special 2-level inheritance mode.
516 $(buildObject "PartialIPolicy" "ipolicy"
517 [ optionalField . renameField "MinMaxISpecsP"
518 $ simpleField C.ispecsMinmax [t| [MinMaxISpecs] |]
519 , optionalField . renameField "StdSpecP"
520 $ simpleField "std" [t| PartialISpecParams |]
521 , optionalField . renameField "SpindleRatioP"
522 $ simpleField "spindle-ratio" [t| Double |]
523 , optionalField . renameField "VcpuRatioP"
524 $ simpleField "vcpu-ratio" [t| Double |]
525 , optionalField . renameField "DiskTemplatesP"
526 $ simpleField "disk-templates" [t| [DiskTemplate] |]
529 -- | Custom filled ipolicy. This is not built via buildParam since it
530 -- has a special 2-level inheritance mode.
531 $(buildObject "FilledIPolicy" "ipolicy"
532 [ renameField "MinMaxISpecs"
533 $ simpleField C.ispecsMinmax [t| [MinMaxISpecs] |]
534 , renameField "StdSpec" $ simpleField "std" [t| FilledISpecParams |]
535 , simpleField "spindle-ratio" [t| Double |]
536 , simpleField "vcpu-ratio" [t| Double |]
537 , simpleField "disk-templates" [t| [DiskTemplate] |]
540 -- | Custom filler for the ipolicy types.
541 fillIPolicy :: FilledIPolicy -> PartialIPolicy -> FilledIPolicy
542 fillIPolicy (FilledIPolicy { ipolicyMinMaxISpecs = fminmax
543 , ipolicyStdSpec = fstd
544 , ipolicySpindleRatio = fspindleRatio
545 , ipolicyVcpuRatio = fvcpuRatio
546 , ipolicyDiskTemplates = fdiskTemplates})
547 (PartialIPolicy { ipolicyMinMaxISpecsP = pminmax
548 , ipolicyStdSpecP = pstd
549 , ipolicySpindleRatioP = pspindleRatio
550 , ipolicyVcpuRatioP = pvcpuRatio
551 , ipolicyDiskTemplatesP = pdiskTemplates}) =
552 FilledIPolicy { ipolicyMinMaxISpecs = fromMaybe fminmax pminmax
553 , ipolicyStdSpec = case pstd of
555 Just p -> fillISpecParams fstd p
556 , ipolicySpindleRatio = fromMaybe fspindleRatio pspindleRatio
557 , ipolicyVcpuRatio = fromMaybe fvcpuRatio pvcpuRatio
558 , ipolicyDiskTemplates = fromMaybe fdiskTemplates
561 -- * Node definitions
563 $(buildParam "ND" "ndp"
564 [ simpleField "oob_program" [t| String |]
565 , simpleField "spindle_count" [t| Int |]
566 , simpleField "exclusive_storage" [t| Bool |]
569 $(buildObject "Node" "node" $
570 [ simpleField "name" [t| String |]
571 , simpleField "primary_ip" [t| String |]
572 , simpleField "secondary_ip" [t| String |]
573 , simpleField "master_candidate" [t| Bool |]
574 , simpleField "offline" [t| Bool |]
575 , simpleField "drained" [t| Bool |]
576 , simpleField "group" [t| String |]
577 , simpleField "master_capable" [t| Bool |]
578 , simpleField "vm_capable" [t| Bool |]
579 , simpleField "ndparams" [t| PartialNDParams |]
580 , simpleField "powered" [t| Bool |]
587 instance TimeStampObject Node where
591 instance UuidObject Node where
594 instance SerialNoObject Node where
595 serialOf = nodeSerial
597 instance TagsObject Node where
600 -- * NodeGroup definitions
602 -- | The disk parameters type.
603 type DiskParams = Container (Container JSValue)
605 -- | A mapping from network UUIDs to nic params of the networks.
606 type Networks = Container PartialNicParams
608 $(buildObject "NodeGroup" "group" $
609 [ simpleField "name" [t| String |]
610 , defaultField [| [] |] $ simpleField "members" [t| [String] |]
611 , simpleField "ndparams" [t| PartialNDParams |]
612 , simpleField "alloc_policy" [t| AllocPolicy |]
613 , simpleField "ipolicy" [t| PartialIPolicy |]
614 , simpleField "diskparams" [t| DiskParams |]
615 , simpleField "networks" [t| Networks |]
622 instance TimeStampObject NodeGroup where
626 instance UuidObject NodeGroup where
629 instance SerialNoObject NodeGroup where
630 serialOf = groupSerial
632 instance TagsObject NodeGroup where
636 $(declareIADT "IpFamily"
637 [ ("IpFamilyV4", 'C.ip4Family)
638 , ("IpFamilyV6", 'C.ip6Family)
640 $(makeJSONInstance ''IpFamily)
642 -- | Conversion from IP family to IP version. This is needed because
643 -- Python uses both, depending on context.
644 ipFamilyToVersion :: IpFamily -> Int
645 ipFamilyToVersion IpFamilyV4 = C.ip4Version
646 ipFamilyToVersion IpFamilyV6 = C.ip6Version
648 -- | Cluster HvParams (hvtype to hvparams mapping).
649 type ClusterHvParams = Container HvParams
651 -- | Cluster Os-HvParams (os to hvparams mapping).
652 type OsHvParams = Container ClusterHvParams
654 -- | Cluser BeParams.
655 type ClusterBeParams = Container FilledBeParams
657 -- | Cluster OsParams.
658 type ClusterOsParams = Container OsParams
660 -- | Cluster NicParams.
661 type ClusterNicParams = Container FilledNicParams
663 -- | Cluster UID Pool, list (low, high) UID ranges.
664 type UidPool = [(Int, Int)]
666 -- * Cluster definitions
667 $(buildObject "Cluster" "cluster" $
668 [ simpleField "rsahostkeypub" [t| String |]
669 , simpleField "highest_used_port" [t| Int |]
670 , simpleField "tcpudp_port_pool" [t| [Int] |]
671 , simpleField "mac_prefix" [t| String |]
673 simpleField "volume_group_name" [t| String |]
674 , simpleField "reserved_lvs" [t| [String] |]
676 simpleField "drbd_usermode_helper" [t| String |]
677 , simpleField "master_node" [t| String |]
678 , simpleField "master_ip" [t| String |]
679 , simpleField "master_netdev" [t| String |]
680 , simpleField "master_netmask" [t| Int |]
681 , simpleField "use_external_mip_script" [t| Bool |]
682 , simpleField "cluster_name" [t| String |]
683 , simpleField "file_storage_dir" [t| String |]
684 , simpleField "shared_file_storage_dir" [t| String |]
685 , simpleField "enabled_hypervisors" [t| [Hypervisor] |]
686 , simpleField "hvparams" [t| ClusterHvParams |]
687 , simpleField "os_hvp" [t| OsHvParams |]
688 , simpleField "beparams" [t| ClusterBeParams |]
689 , simpleField "osparams" [t| ClusterOsParams |]
690 , simpleField "nicparams" [t| ClusterNicParams |]
691 , simpleField "ndparams" [t| FilledNDParams |]
692 , simpleField "diskparams" [t| DiskParams |]
693 , simpleField "candidate_pool_size" [t| Int |]
694 , simpleField "modify_etc_hosts" [t| Bool |]
695 , simpleField "modify_ssh_setup" [t| Bool |]
696 , simpleField "maintain_node_health" [t| Bool |]
697 , simpleField "uid_pool" [t| UidPool |]
698 , simpleField "default_iallocator" [t| String |]
699 , simpleField "hidden_os" [t| [String] |]
700 , simpleField "blacklisted_os" [t| [String] |]
701 , simpleField "primary_ip_family" [t| IpFamily |]
702 , simpleField "prealloc_wipe_disks" [t| Bool |]
703 , simpleField "ipolicy" [t| FilledIPolicy |]
704 , simpleField "enabled_disk_templates" [t| [DiskTemplate] |]
711 instance TimeStampObject Cluster where
712 cTimeOf = clusterCtime
713 mTimeOf = clusterMtime
715 instance UuidObject Cluster where
718 instance SerialNoObject Cluster where
719 serialOf = clusterSerial
721 instance TagsObject Cluster where
724 -- * ConfigData definitions
726 $(buildObject "ConfigData" "config" $
727 -- timeStampFields ++
728 [ simpleField "version" [t| Int |]
729 , simpleField "cluster" [t| Cluster |]
730 , simpleField "nodes" [t| Container Node |]
731 , simpleField "nodegroups" [t| Container NodeGroup |]
732 , simpleField "instances" [t| Container Instance |]
733 , simpleField "networks" [t| Container Network |]
737 instance SerialNoObject ConfigData where
738 serialOf = configSerial