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(..)
66 , FilledISpecParams(..)
67 , PartialISpecParams(..)
90 , DictObject(..) -- re-exported from THH
91 , TagSet -- re-exported from THH
99 import Control.Applicative
100 import Data.List (foldl')
102 import qualified Data.Map as Map
103 import qualified Data.Set as Set
105 import Text.JSON (showJSON, readJSON, JSON, JSValue(..), fromJSString)
106 import qualified Text.JSON as J
108 import qualified Ganeti.Constants as C
112 import Ganeti.Utils (sepSplit, tryRead)
114 -- * Generic definitions
116 -- | Fills one map with keys from the other map, if not already
117 -- existing. Mirrors objects.py:FillDict.
118 fillDict :: (Ord k) => Map.Map k v -> Map.Map k v -> [k] -> Map.Map k v
119 fillDict defaults custom skip_keys =
120 let updated = Map.union custom defaults
121 in foldl' (flip Map.delete) updated skip_keys
123 -- | The VTYPES, a mini-type system in Python.
124 $(declareSADT "VType"
125 [ ("VTypeString", 'C.vtypeString)
126 , ("VTypeMaybeString", 'C.vtypeMaybeString)
127 , ("VTypeBool", 'C.vtypeBool)
128 , ("VTypeSize", 'C.vtypeSize)
129 , ("VTypeInt", 'C.vtypeInt)
131 $(makeJSONInstance ''VType)
133 -- | The hypervisor parameter type. This is currently a simple map,
134 -- without type checking on key/value pairs.
135 type HvParams = Container JSValue
137 -- | The OS parameters type. This is, and will remain, a string
138 -- container, since the keys are dynamically declared by the OSes, and
139 -- the values are always strings.
140 type OsParams = Container String
142 -- | Class of objects that have timestamps.
143 class TimeStampObject a where
144 cTimeOf :: a -> Double
145 mTimeOf :: a -> Double
147 -- | Class of objects that have an UUID.
148 class UuidObject a where
149 uuidOf :: a -> String
151 -- | Class of object that have a serial number.
152 class SerialNoObject a where
155 -- | Class of objects that have tags.
156 class TagsObject a where
157 tagsOf :: a -> Set.Set String
159 -- * Node role object
161 $(declareSADT "NodeRole"
162 [ ("NROffline", 'C.nrOffline)
163 , ("NRDrained", 'C.nrDrained)
164 , ("NRRegular", 'C.nrRegular)
165 , ("NRCandidate", 'C.nrMcandidate)
166 , ("NRMaster", 'C.nrMaster)
168 $(makeJSONInstance ''NodeRole)
170 -- | The description of the node role.
171 roleDescription :: NodeRole -> String
172 roleDescription NROffline = "offline"
173 roleDescription NRDrained = "drained"
174 roleDescription NRRegular = "regular"
175 roleDescription NRCandidate = "master candidate"
176 roleDescription NRMaster = "master"
178 -- * Network definitions
182 -- | Custom type for a simple IPv4 address.
183 data Ip4Address = Ip4Address Word8 Word8 Word8 Word8
186 instance Show Ip4Address where
187 show (Ip4Address a b c d) = show a ++ "." ++ show b ++ "." ++
188 show c ++ "." ++ show d
190 -- | Parses an IPv4 address from a string.
191 readIp4Address :: (Applicative m, Monad m) => String -> m Ip4Address
193 case sepSplit '.' s of
194 [a, b, c, d] -> Ip4Address <$>
195 tryRead "first octect" a <*>
196 tryRead "second octet" b <*>
197 tryRead "third octet" c <*>
198 tryRead "fourth octet" d
199 _ -> fail $ "Can't parse IPv4 address from string " ++ s
201 -- | JSON instance for 'Ip4Address'.
202 instance JSON Ip4Address where
203 showJSON = showJSON . show
204 readJSON (JSString s) = readIp4Address (fromJSString s)
205 readJSON v = fail $ "Invalid JSON value " ++ show v ++ " for an IPv4 address"
207 -- | \"Next\" address implementation for IPv4 addresses.
209 -- Note that this loops! Note also that this is a very dumb
211 nextIp4Address :: Ip4Address -> Ip4Address
212 nextIp4Address (Ip4Address a b c d) =
213 let inc xs y = if all (==0) xs then y + 1 else y
217 a' = inc [b', c', d'] a
218 in Ip4Address a' b' c' d'
220 -- | Custom type for an IPv4 network.
221 data Ip4Network = Ip4Network Ip4Address Word8
224 instance Show Ip4Network where
225 show (Ip4Network ip netmask) = show ip ++ "/" ++ show netmask
227 -- | JSON instance for 'Ip4Network'.
228 instance JSON Ip4Network where
229 showJSON = showJSON . show
230 readJSON (JSString s) =
231 case sepSplit '/' (fromJSString s) of
233 ip' <- readIp4Address ip
234 nm' <- tryRead "parsing netmask" nm
235 if nm' >= 0 && nm' <= 32
236 then return $ Ip4Network ip' nm'
237 else fail $ "Invalid netmask " ++ show nm' ++ " from string " ++
239 _ -> fail $ "Can't parse IPv4 network from string " ++ fromJSString s
240 readJSON v = fail $ "Invalid JSON value " ++ show v ++ " for an IPv4 network"
242 -- ** Ganeti \"network\" config object.
244 -- FIXME: Not all types might be correct here, since they
245 -- haven't been exhaustively deduced from the python code yet.
246 $(buildObject "Network" "network" $
247 [ simpleField "name" [t| NonEmptyString |]
249 simpleField "mac_prefix" [t| String |]
250 , simpleField "network" [t| Ip4Network |]
252 simpleField "network6" [t| String |]
254 simpleField "gateway" [t| Ip4Address |]
256 simpleField "gateway6" [t| String |]
258 simpleField "reservations" [t| String |]
260 simpleField "ext_reservations" [t| String |]
267 instance SerialNoObject Network where
268 serialOf = networkSerial
270 instance TagsObject Network where
273 instance UuidObject Network where
276 instance TimeStampObject Network where
277 cTimeOf = networkCtime
278 mTimeOf = networkMtime
282 $(buildParam "Nic" "nicp"
283 [ simpleField "mode" [t| NICMode |]
284 , simpleField "link" [t| String |]
287 $(buildObject "PartialNic" "nic" $
288 [ simpleField "mac" [t| String |]
289 , optionalField $ simpleField "ip" [t| String |]
290 , simpleField "nicparams" [t| PartialNicParams |]
291 , optionalField $ simpleField "network" [t| String |]
292 , optionalField $ simpleField "name" [t| String |]
295 instance UuidObject PartialNic where
298 -- * Disk definitions
300 $(declareSADT "DiskMode"
301 [ ("DiskRdOnly", 'C.diskRdonly)
302 , ("DiskRdWr", 'C.diskRdwr)
304 $(makeJSONInstance ''DiskMode)
306 $(declareSADT "DiskType"
308 , ("LD_DRBD8", 'C.ldDrbd8)
309 , ("LD_FILE", 'C.ldFile)
310 , ("LD_BLOCKDEV", 'C.ldBlockdev)
311 , ("LD_RADOS", 'C.ldRbd)
312 , ("LD_EXT", 'C.ldExt)
314 $(makeJSONInstance ''DiskType)
316 -- | The persistent block driver type. Currently only one type is allowed.
317 $(declareSADT "BlockDriver"
318 [ ("BlockDrvManual", 'C.blockdevDriverManual)
320 $(makeJSONInstance ''BlockDriver)
322 -- | Constant for the dev_type key entry in the disk config.
326 -- | The disk configuration type. This includes the disk type itself,
327 -- for a more complete consistency. Note that since in the Python
328 -- code-base there's no authoritative place where we document the
329 -- logical id, this is probably a good reference point.
331 = LIDPlain String String -- ^ Volume group, logical volume
332 | LIDDrbd8 String String Int Int Int String
333 -- ^ NodeA, NodeB, Port, MinorA, MinorB, Secret
334 | LIDFile FileDriver String -- ^ Driver, path
335 | LIDBlockDev BlockDriver String -- ^ Driver, path (must be under /dev)
336 | LIDRados String String -- ^ Unused, path
337 | LIDExt String String -- ^ ExtProvider, unique name
340 -- | Mapping from a logical id to a disk type.
341 lidDiskType :: DiskLogicalId -> DiskType
342 lidDiskType (LIDPlain {}) = LD_LV
343 lidDiskType (LIDDrbd8 {}) = LD_DRBD8
344 lidDiskType (LIDFile {}) = LD_FILE
345 lidDiskType (LIDBlockDev {}) = LD_BLOCKDEV
346 lidDiskType (LIDRados {}) = LD_RADOS
347 lidDiskType (LIDExt {}) = LD_EXT
349 -- | Builds the extra disk_type field for a given logical id.
350 lidEncodeType :: DiskLogicalId -> [(String, JSValue)]
351 lidEncodeType v = [(devType, showJSON . lidDiskType $ v)]
353 -- | Custom encoder for DiskLogicalId (logical id only).
354 encodeDLId :: DiskLogicalId -> JSValue
355 encodeDLId (LIDPlain vg lv) = JSArray [showJSON vg, showJSON lv]
356 encodeDLId (LIDDrbd8 nodeA nodeB port minorA minorB key) =
357 JSArray [ showJSON nodeA, showJSON nodeB, showJSON port
358 , showJSON minorA, showJSON minorB, showJSON key ]
359 encodeDLId (LIDRados pool name) = JSArray [showJSON pool, showJSON name]
360 encodeDLId (LIDFile driver name) = JSArray [showJSON driver, showJSON name]
361 encodeDLId (LIDBlockDev driver name) = JSArray [showJSON driver, showJSON name]
362 encodeDLId (LIDExt extprovider name) =
363 JSArray [showJSON extprovider, showJSON name]
365 -- | Custom encoder for DiskLogicalId, composing both the logical id
366 -- and the extra disk_type field.
367 encodeFullDLId :: DiskLogicalId -> (JSValue, [(String, JSValue)])
368 encodeFullDLId v = (encodeDLId v, lidEncodeType v)
370 -- | Custom decoder for DiskLogicalId. This is manual for now, since
371 -- we don't have yet automation for separate-key style fields.
372 decodeDLId :: [(String, JSValue)] -> JSValue -> J.Result DiskLogicalId
373 decodeDLId obj lid = do
374 dtype <- fromObj obj devType
378 JSArray [nA, nB, p, mA, mB, k] -> do
385 return $ LIDDrbd8 nA' nB' p' mA' mB' k'
386 _ -> fail "Can't read logical_id for DRBD8 type"
389 JSArray [vg, lv] -> do
392 return $ LIDPlain vg' lv'
393 _ -> fail "Can't read logical_id for plain type"
396 JSArray [driver, path] -> do
397 driver' <- readJSON driver
398 path' <- readJSON path
399 return $ LIDFile driver' path'
400 _ -> fail "Can't read logical_id for file type"
403 JSArray [driver, path] -> do
404 driver' <- readJSON driver
405 path' <- readJSON path
406 return $ LIDBlockDev driver' path'
407 _ -> fail "Can't read logical_id for blockdev type"
410 JSArray [driver, path] -> do
411 driver' <- readJSON driver
412 path' <- readJSON path
413 return $ LIDRados driver' path'
414 _ -> fail "Can't read logical_id for rdb type"
417 JSArray [extprovider, name] -> do
418 extprovider' <- readJSON extprovider
419 name' <- readJSON name
420 return $ LIDExt extprovider' name'
421 _ -> fail "Can't read logical_id for extstorage type"
423 -- | Disk data structure.
425 -- This is declared manually as it's a recursive structure, and our TH
426 -- code currently can't build it.
428 { diskLogicalId :: DiskLogicalId
429 -- , diskPhysicalId :: String
430 , diskChildren :: [Disk]
431 , diskIvName :: String
433 , diskMode :: DiskMode
434 , diskName :: Maybe String
435 , diskSpindles :: Maybe Int
437 } deriving (Show, Eq)
439 $(buildObjectSerialisation "Disk" $
440 [ customField 'decodeDLId 'encodeFullDLId ["dev_type"] $
441 simpleField "logical_id" [t| DiskLogicalId |]
442 -- , simpleField "physical_id" [t| String |]
443 , defaultField [| [] |] $ simpleField "children" [t| [Disk] |]
444 , defaultField [| "" |] $ simpleField "iv_name" [t| String |]
445 , simpleField "size" [t| Int |]
446 , defaultField [| DiskRdWr |] $ simpleField "mode" [t| DiskMode |]
447 , optionalField $ simpleField "name" [t| String |]
448 , optionalField $ simpleField "spindles" [t| Int |]
452 instance UuidObject Disk where
455 -- | Determines whether a disk or one of his children has the given logical id
456 -- (determined by the volume group name and by the logical volume name).
457 -- This can be true only for DRBD or LVM disks.
458 includesLogicalId :: String -> String -> Disk -> Bool
459 includesLogicalId vg_name lv_name disk =
460 case diskLogicalId disk of
461 LIDPlain vg lv -> vg_name == vg && lv_name == lv
463 any (includesLogicalId vg_name lv_name) $ diskChildren disk
467 -- * Instance definitions
469 $(declareSADT "AdminState"
470 [ ("AdminOffline", 'C.adminstOffline)
471 , ("AdminDown", 'C.adminstDown)
472 , ("AdminUp", 'C.adminstUp)
474 $(makeJSONInstance ''AdminState)
476 $(buildParam "Be" "bep"
477 [ simpleField "minmem" [t| Int |]
478 , simpleField "maxmem" [t| Int |]
479 , simpleField "vcpus" [t| Int |]
480 , simpleField "auto_balance" [t| Bool |]
483 $(buildObject "Instance" "inst" $
484 [ simpleField "name" [t| String |]
485 , simpleField "primary_node" [t| String |]
486 , simpleField "os" [t| String |]
487 , simpleField "hypervisor" [t| Hypervisor |]
488 , simpleField "hvparams" [t| HvParams |]
489 , simpleField "beparams" [t| PartialBeParams |]
490 , simpleField "osparams" [t| OsParams |]
491 , simpleField "admin_state" [t| AdminState |]
492 , simpleField "nics" [t| [PartialNic] |]
493 , simpleField "disks" [t| [Disk] |]
494 , simpleField "disk_template" [t| DiskTemplate |]
495 , simpleField "disks_active" [t| Bool |]
496 , optionalField $ simpleField "network_port" [t| Int |]
503 instance TimeStampObject Instance where
507 instance UuidObject Instance where
510 instance SerialNoObject Instance where
511 serialOf = instSerial
513 instance TagsObject Instance where
516 -- * IPolicy definitions
518 $(buildParam "ISpec" "ispec"
519 [ simpleField C.ispecMemSize [t| Int |]
520 , simpleField C.ispecDiskSize [t| Int |]
521 , simpleField C.ispecDiskCount [t| Int |]
522 , simpleField C.ispecCpuCount [t| Int |]
523 , simpleField C.ispecNicCount [t| Int |]
524 , simpleField C.ispecSpindleUse [t| Int |]
527 $(buildObject "MinMaxISpecs" "mmis"
528 [ renameField "MinSpec" $ simpleField "min" [t| FilledISpecParams |]
529 , renameField "MaxSpec" $ simpleField "max" [t| FilledISpecParams |]
532 -- | Custom partial ipolicy. This is not built via buildParam since it
533 -- has a special 2-level inheritance mode.
534 $(buildObject "PartialIPolicy" "ipolicy"
535 [ optionalField . renameField "MinMaxISpecsP"
536 $ simpleField C.ispecsMinmax [t| [MinMaxISpecs] |]
537 , optionalField . renameField "StdSpecP"
538 $ simpleField "std" [t| PartialISpecParams |]
539 , optionalField . renameField "SpindleRatioP"
540 $ simpleField "spindle-ratio" [t| Double |]
541 , optionalField . renameField "VcpuRatioP"
542 $ simpleField "vcpu-ratio" [t| Double |]
543 , optionalField . renameField "DiskTemplatesP"
544 $ simpleField "disk-templates" [t| [DiskTemplate] |]
547 -- | Custom filled ipolicy. This is not built via buildParam since it
548 -- has a special 2-level inheritance mode.
549 $(buildObject "FilledIPolicy" "ipolicy"
550 [ renameField "MinMaxISpecs"
551 $ simpleField C.ispecsMinmax [t| [MinMaxISpecs] |]
552 , renameField "StdSpec" $ simpleField "std" [t| FilledISpecParams |]
553 , simpleField "spindle-ratio" [t| Double |]
554 , simpleField "vcpu-ratio" [t| Double |]
555 , simpleField "disk-templates" [t| [DiskTemplate] |]
558 -- | Custom filler for the ipolicy types.
559 fillIPolicy :: FilledIPolicy -> PartialIPolicy -> FilledIPolicy
560 fillIPolicy (FilledIPolicy { ipolicyMinMaxISpecs = fminmax
561 , ipolicyStdSpec = fstd
562 , ipolicySpindleRatio = fspindleRatio
563 , ipolicyVcpuRatio = fvcpuRatio
564 , ipolicyDiskTemplates = fdiskTemplates})
565 (PartialIPolicy { ipolicyMinMaxISpecsP = pminmax
566 , ipolicyStdSpecP = pstd
567 , ipolicySpindleRatioP = pspindleRatio
568 , ipolicyVcpuRatioP = pvcpuRatio
569 , ipolicyDiskTemplatesP = pdiskTemplates}) =
570 FilledIPolicy { ipolicyMinMaxISpecs = fromMaybe fminmax pminmax
571 , ipolicyStdSpec = case pstd of
573 Just p -> fillISpecParams fstd p
574 , ipolicySpindleRatio = fromMaybe fspindleRatio pspindleRatio
575 , ipolicyVcpuRatio = fromMaybe fvcpuRatio pvcpuRatio
576 , ipolicyDiskTemplates = fromMaybe fdiskTemplates
579 -- * Node definitions
581 $(buildParam "ND" "ndp"
582 [ simpleField "oob_program" [t| String |]
583 , simpleField "spindle_count" [t| Int |]
584 , simpleField "exclusive_storage" [t| Bool |]
587 $(buildObject "Node" "node" $
588 [ simpleField "name" [t| String |]
589 , simpleField "primary_ip" [t| String |]
590 , simpleField "secondary_ip" [t| String |]
591 , simpleField "master_candidate" [t| Bool |]
592 , simpleField "offline" [t| Bool |]
593 , simpleField "drained" [t| Bool |]
594 , simpleField "group" [t| String |]
595 , simpleField "master_capable" [t| Bool |]
596 , simpleField "vm_capable" [t| Bool |]
597 , simpleField "ndparams" [t| PartialNDParams |]
598 , simpleField "powered" [t| Bool |]
605 instance TimeStampObject Node where
609 instance UuidObject Node where
612 instance SerialNoObject Node where
613 serialOf = nodeSerial
615 instance TagsObject Node where
618 -- * NodeGroup definitions
620 -- | The disk parameters type.
621 type DiskParams = Container (Container JSValue)
623 -- | A mapping from network UUIDs to nic params of the networks.
624 type Networks = Container PartialNicParams
626 $(buildObject "NodeGroup" "group" $
627 [ simpleField "name" [t| String |]
628 , defaultField [| [] |] $ simpleField "members" [t| [String] |]
629 , simpleField "ndparams" [t| PartialNDParams |]
630 , simpleField "alloc_policy" [t| AllocPolicy |]
631 , simpleField "ipolicy" [t| PartialIPolicy |]
632 , simpleField "diskparams" [t| DiskParams |]
633 , simpleField "networks" [t| Networks |]
640 instance TimeStampObject NodeGroup where
644 instance UuidObject NodeGroup where
647 instance SerialNoObject NodeGroup where
648 serialOf = groupSerial
650 instance TagsObject NodeGroup where
654 $(declareIADT "IpFamily"
655 [ ("IpFamilyV4", 'C.ip4Family)
656 , ("IpFamilyV6", 'C.ip6Family)
658 $(makeJSONInstance ''IpFamily)
660 -- | Conversion from IP family to IP version. This is needed because
661 -- Python uses both, depending on context.
662 ipFamilyToVersion :: IpFamily -> Int
663 ipFamilyToVersion IpFamilyV4 = C.ip4Version
664 ipFamilyToVersion IpFamilyV6 = C.ip6Version
666 -- | Cluster HvParams (hvtype to hvparams mapping).
667 type ClusterHvParams = Container HvParams
669 -- | Cluster Os-HvParams (os to hvparams mapping).
670 type OsHvParams = Container ClusterHvParams
672 -- | Cluser BeParams.
673 type ClusterBeParams = Container FilledBeParams
675 -- | Cluster OsParams.
676 type ClusterOsParams = Container OsParams
678 -- | Cluster NicParams.
679 type ClusterNicParams = Container FilledNicParams
681 -- | Cluster UID Pool, list (low, high) UID ranges.
682 type UidPool = [(Int, Int)]
684 -- * Cluster definitions
685 $(buildObject "Cluster" "cluster" $
686 [ simpleField "rsahostkeypub" [t| String |]
687 , simpleField "dsahostkeypub" [t| String |]
688 , simpleField "highest_used_port" [t| Int |]
689 , simpleField "tcpudp_port_pool" [t| [Int] |]
690 , simpleField "mac_prefix" [t| String |]
692 simpleField "volume_group_name" [t| String |]
693 , simpleField "reserved_lvs" [t| [String] |]
695 simpleField "drbd_usermode_helper" [t| String |]
696 , simpleField "master_node" [t| String |]
697 , simpleField "master_ip" [t| String |]
698 , simpleField "master_netdev" [t| String |]
699 , simpleField "master_netmask" [t| Int |]
700 , simpleField "use_external_mip_script" [t| Bool |]
701 , simpleField "cluster_name" [t| String |]
702 , simpleField "file_storage_dir" [t| String |]
703 , simpleField "shared_file_storage_dir" [t| String |]
704 , simpleField "enabled_hypervisors" [t| [Hypervisor] |]
705 , simpleField "hvparams" [t| ClusterHvParams |]
706 , simpleField "os_hvp" [t| OsHvParams |]
707 , simpleField "beparams" [t| ClusterBeParams |]
708 , simpleField "osparams" [t| ClusterOsParams |]
709 , simpleField "nicparams" [t| ClusterNicParams |]
710 , simpleField "ndparams" [t| FilledNDParams |]
711 , simpleField "diskparams" [t| DiskParams |]
712 , simpleField "candidate_pool_size" [t| Int |]
713 , simpleField "modify_etc_hosts" [t| Bool |]
714 , simpleField "modify_ssh_setup" [t| Bool |]
715 , simpleField "maintain_node_health" [t| Bool |]
716 , simpleField "uid_pool" [t| UidPool |]
717 , simpleField "default_iallocator" [t| String |]
718 , simpleField "hidden_os" [t| [String] |]
719 , simpleField "blacklisted_os" [t| [String] |]
720 , simpleField "primary_ip_family" [t| IpFamily |]
721 , simpleField "prealloc_wipe_disks" [t| Bool |]
722 , simpleField "ipolicy" [t| FilledIPolicy |]
723 , simpleField "enabled_disk_templates" [t| [DiskTemplate] |]
730 instance TimeStampObject Cluster where
731 cTimeOf = clusterCtime
732 mTimeOf = clusterMtime
734 instance UuidObject Cluster where
737 instance SerialNoObject Cluster where
738 serialOf = clusterSerial
740 instance TagsObject Cluster where
743 -- * ConfigData definitions
745 $(buildObject "ConfigData" "config" $
746 -- timeStampFields ++
747 [ simpleField "version" [t| Int |]
748 , simpleField "cluster" [t| Cluster |]
749 , simpleField "nodes" [t| Container Node |]
750 , simpleField "nodegroups" [t| Container NodeGroup |]
751 , simpleField "instances" [t| Container Instance |]
752 , simpleField "networks" [t| Container Network |]
756 instance SerialNoObject ConfigData where
757 serialOf = configSerial