{-# LANGUAGE TemplateHaskell, FunctionalDependencies #-}

{-| Implementation of the Ganeti config objects.

-}

{-

Copyright (C) 2011, 2012, 2013, 2014 Google Inc.
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:

1. Redistributions of source code must retain the above copyright notice,
this list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

-}

module Ganeti.Objects
  ( HvParams
  , OsParams
  , OsParamsPrivate
  , PartialNicParams(..)
  , FilledNicParams(..)
  , allNicParamFields
  , PartialNic(..)
  , FileDriver(..)
  , DataCollectorConfig(..)
  , DiskTemplate(..)
  , PartialBeParams(..)
  , FilledBeParams(..)
  , PartialNDParams(..)
  , FilledNDParams(..)
  , allNDParamFields
  , Node(..)
  , AllocPolicy(..)
  , FilledISpecParams(..)
  , PartialISpecParams(..)
  , allISpecParamFields
  , MinMaxISpecs(..)
  , FilledIPolicy(..)
  , PartialIPolicy(..)
  , GroupDiskParams
  , NodeGroup(..)
  , FilterAction(..)
  , FilterPredicate(..)
  , FilterRule(..)
  , filterRuleOrder
  , IpFamily(..)
  , ipFamilyToRaw
  , ipFamilyToVersion
  , fillDict
  , ClusterHvParams
  , OsHvParams
  , ClusterBeParams
  , ClusterOsParams
  , ClusterOsParamsPrivate
  , ClusterNicParams
  , UidPool
  , formatUidRange
  , UidRange
  , Cluster(..)
  , ConfigData(..)
  , TimeStampObject(..) -- re-exported from Types
  , UuidObject(..) -- re-exported from Types
  , SerialNoObject(..) -- re-exported from Types
  , TagsObject(..) -- re-exported from Types
  , DictObject(..) -- re-exported from THH
  , TagSet -- re-exported from THH
  , Network(..)
  , AddressPool(..)
  , Ip4Address()
  , mkIp4Address
  , Ip4Network()
  , mkIp4Network
  , ip4netAddr
  , ip4netMask
  , readIp4Address
  , ip4AddressToList
  , ip4AddressToNumber
  , ip4AddressFromNumber
  , nextIp4Address
  , IAllocatorParams
  , MasterNetworkParameters(..)
  , module Ganeti.PartialParams
  , module Ganeti.Objects.Disk
  , module Ganeti.Objects.Instance
  , module Ganeti.Objects.Maintenance
  , FilledHvStateParams(..)
  , PartialHvStateParams(..)
  , allHvStateParamFields
  , FilledHvState
  , PartialHvState ) where

import Prelude ()
import Ganeti.Prelude

import Control.Arrow (first)
import Control.Monad.State
import qualified Data.ByteString.UTF8 as UTF8
import Data.List (foldl', intercalate)
import Data.Maybe
import qualified Data.Map as Map
import Data.Ord (comparing)
import Data.Ratio (numerator, denominator)
import Data.Tuple (swap)
import Data.Word
import Text.JSON (showJSON, readJSON, JSON, JSValue(..), fromJSString,
                  toJSString)
import qualified Text.JSON as J

import qualified AutoConf
import qualified Ganeti.Constants as C
import qualified Ganeti.ConstantUtils as ConstantUtils
import Ganeti.JSON
import Ganeti.Objects.BitArray (BitArray)
import Ganeti.Objects.Disk
import Ganeti.Objects.Maintenance
import Ganeti.Objects.Nic
import Ganeti.Objects.Instance
import Ganeti.Objects.HvState
import Ganeti.Query.Language
import Ganeti.PartialParams
import Ganeti.Types
import Ganeti.THH
import Ganeti.THH.Field
import Ganeti.Utils (sepSplit, tryRead)

-- * Generic definitions

-- | Fills one map with keys from the other map, if not already
-- existing. Mirrors objects.py:FillDict.
fillDict :: (Ord k) => Map.Map k v -> Map.Map k v -> [k] -> Map.Map k v
fillDict defaults custom skip_keys =
  let updated = Map.union custom defaults
  in foldl' (flip Map.delete) updated skip_keys


-- * Network definitions

-- ** Ipv4 types

data Ip4Address = Ip4Address Word8 Word8 Word8 Word8
  deriving (Eq, Ord)

mkIp4Address :: (Word8, Word8, Word8, Word8) -> Ip4Address
mkIp4Address (a, b, c, d) = Ip4Address a b c d

instance Show Ip4Address where
  show (Ip4Address a b c d) = intercalate "." $ map show [a, b, c, d]

readIp4Address :: (Applicative m, Monad m) => String -> m Ip4Address
readIp4Address s =
  case sepSplit '.' s of
    [a, b, c, d] -> Ip4Address <$>
                      tryRead "first octect" a <*>
                      tryRead "second octet" b <*>
                      tryRead "third octet"  c <*>
                      tryRead "fourth octet" d
    _ -> fail $ "Can't parse IPv4 address from string " ++ s

instance JSON Ip4Address where
  showJSON = showJSON . show
  readJSON (JSString s) = readIp4Address (fromJSString s)
  readJSON v = fail $ "Invalid JSON value " ++ show v ++ " for an IPv4 address"

-- Converts an address to a list of numbers
ip4AddressToList :: Ip4Address -> [Word8]
ip4AddressToList (Ip4Address a b c d) = [a, b, c, d]

-- | Converts an address into its ordinal number.
-- This is needed for indexing IP adresses in reservation pools.
ip4AddressToNumber :: Ip4Address -> Integer
ip4AddressToNumber = foldl (\n i -> 256 * n + toInteger i) 0 . ip4AddressToList

-- | Converts a number into an address.
-- This is needed for indexing IP adresses in reservation pools.
ip4AddressFromNumber :: Integer -> Ip4Address
ip4AddressFromNumber n =
  let s = state $ first fromInteger . swap . (`divMod` 256)
      (d, c, b, a) = evalState ((,,,) <$> s <*> s <*> s <*> s) n
   in Ip4Address a b c d

nextIp4Address :: Ip4Address -> Ip4Address
nextIp4Address = ip4AddressFromNumber . (+ 1) . ip4AddressToNumber

-- | Custom type for an IPv4 network.
data Ip4Network = Ip4Network { ip4netAddr :: Ip4Address
                             , ip4netMask :: Word8
                             }
                  deriving (Eq)

mkIp4Network :: Ip4Address -> Word8 -> Ip4Network
mkIp4Network = Ip4Network

instance Show Ip4Network where
  show (Ip4Network ip netmask) = show ip ++ "/" ++ show netmask

-- | JSON instance for 'Ip4Network'.
instance JSON Ip4Network where
  showJSON = showJSON . show
  readJSON (JSString s) =
    case sepSplit '/' (fromJSString s) of
      [ip, nm] -> do
        ip' <- readIp4Address ip
        nm' <- tryRead "parsing netmask" nm
        if nm' >= 0 && nm' <= 32
          then return $ Ip4Network ip' nm'
          else fail $ "Invalid netmask " ++ show nm' ++ " from string " ++
                      fromJSString s
      _ -> fail $ "Can't parse IPv4 network from string " ++ fromJSString s
  readJSON v = fail $ "Invalid JSON value " ++ show v ++ " for an IPv4 network"

-- ** Address pools

-- | Currently address pools just wrap a reservation 'BitArray'.
--
-- In future, 'Network' might be extended to include several address pools
-- and address pools might include their own ranges of addresses.
newtype AddressPool = AddressPool { apReservations :: BitArray }
  deriving (Eq, Ord, Show)

instance JSON AddressPool where
  showJSON = showJSON . apReservations
  readJSON = liftM AddressPool . readJSON

-- ** Ganeti \"network\" config object.

-- FIXME: Not all types might be correct here, since they
-- haven't been exhaustively deduced from the python code yet.
--
-- FIXME: When parsing, check that the ext_reservations and reservations
-- have the same length
$(buildObject "Network" "network" $
  [ simpleField "name"             [t| NonEmptyString |]
  , optionalField $
    simpleField "mac_prefix"       [t| String |]
  , simpleField "network"          [t| Ip4Network |]
  , optionalField $
    simpleField "network6"         [t| String |]
  , optionalField $
    simpleField "gateway"          [t| Ip4Address |]
  , optionalField $
    simpleField "gateway6"         [t| String |]
  , optionalField $
    simpleField "reservations"     [t| AddressPool |]
  , optionalField $
    simpleField "ext_reservations" [t| AddressPool |]
  ]
  ++ uuidFields
  ++ timeStampFields
  ++ serialFields
  ++ tagsFields)

instance SerialNoObject Network where
  serialOf = networkSerial

instance TagsObject Network where
  tagsOf = networkTags

instance UuidObject Network where
  uuidOf = UTF8.toString . networkUuid

instance TimeStampObject Network where
  cTimeOf = networkCtime
  mTimeOf = networkMtime


-- * Datacollector definitions
type MicroSeconds = Integer

-- | The configuration regarding a single data collector.
$(buildObject "DataCollectorConfig" "dataCollector" [
  simpleField "active" [t| Bool|],
  simpleField "interval" [t| MicroSeconds |]
  ])

-- | Central default values of the data collector config.
instance Monoid DataCollectorConfig where
  mempty = DataCollectorConfig
    { dataCollectorActive = True
    , dataCollectorInterval = 10^(6::Integer) * fromIntegral C.mondTimeInterval
    }
  mappend _ a = a

-- * IPolicy definitions

$(buildParam "ISpec" "ispec"
  [ simpleField ConstantUtils.ispecMemSize     [t| Int |]
  , simpleField ConstantUtils.ispecDiskSize    [t| Int |]
  , simpleField ConstantUtils.ispecDiskCount   [t| Int |]
  , simpleField ConstantUtils.ispecCpuCount    [t| Int |]
  , simpleField ConstantUtils.ispecNicCount    [t| Int |]
  , simpleField ConstantUtils.ispecSpindleUse  [t| Int |]
  ])

$(buildObject "MinMaxISpecs" "mmis"
  [ renameField "MinSpec" $ simpleField "min" [t| FilledISpecParams |]
  , renameField "MaxSpec" $ simpleField "max" [t| FilledISpecParams |]
  ])

-- | Custom partial ipolicy. This is not built via buildParam since it
-- has a special 2-level inheritance mode.
$(buildObject "PartialIPolicy" "ipolicy"
  [ optionalField . renameField "MinMaxISpecsP" $
    simpleField ConstantUtils.ispecsMinmax [t| [MinMaxISpecs] |]
  , optionalField . renameField "StdSpecP" $
    simpleField "std" [t| PartialISpecParams |]
  , optionalField . renameField "SpindleRatioP" $
    simpleField "spindle-ratio" [t| Double |]
  , optionalField . renameField "MemoryRatioP" $
    simpleField "memory-ratio" [t| Double |]
  , optionalField . renameField "VcpuRatioP" $
    simpleField "vcpu-ratio" [t| Double |]
  , optionalField . renameField "DiskTemplatesP" $
    simpleField "disk-templates" [t| [DiskTemplate] |]
  ])

-- | Custom filled ipolicy. This is not built via buildParam since it
-- has a special 2-level inheritance mode.
$(buildObject "FilledIPolicy" "ipolicy"
  [ renameField "MinMaxISpecs" $
    simpleField ConstantUtils.ispecsMinmax [t| [MinMaxISpecs] |]
  , renameField "StdSpec" $ simpleField "std" [t| FilledISpecParams |]
  , simpleField "spindle-ratio"  [t| Double |]
  , defaultField [| ConstantUtils.ipolicyDefaultsMemoryRatio |] $
    simpleField "memory-ratio"  [t| Double |]
  , simpleField "vcpu-ratio"     [t| Double |]
  , simpleField "disk-templates" [t| [DiskTemplate] |]
  ])

-- | Custom filler for the ipolicy types.
instance PartialParams FilledIPolicy PartialIPolicy where
  fillParams
            (FilledIPolicy { ipolicyMinMaxISpecs  = fminmax
                           , ipolicyStdSpec       = fstd
                           , ipolicySpindleRatio  = fspindleRatio
                           , ipolicyMemoryRatio   = fmemoryRatio
                           , ipolicyVcpuRatio     = fvcpuRatio
                           , ipolicyDiskTemplates = fdiskTemplates})
            (PartialIPolicy { ipolicyMinMaxISpecsP  = pminmax
                            , ipolicyStdSpecP       = pstd
                            , ipolicySpindleRatioP  = pspindleRatio
                            , ipolicyMemoryRatioP   = pmemoryRatio
                            , ipolicyVcpuRatioP     = pvcpuRatio
                            , ipolicyDiskTemplatesP = pdiskTemplates}) =
    FilledIPolicy
                { ipolicyMinMaxISpecs  = fromMaybe fminmax pminmax
                , ipolicyStdSpec       = maybe fstd (fillParams fstd) pstd
                , ipolicySpindleRatio  = fromMaybe fspindleRatio pspindleRatio
                , ipolicyMemoryRatio   = fromMaybe fmemoryRatio pmemoryRatio
                , ipolicyVcpuRatio     = fromMaybe fvcpuRatio pvcpuRatio
                , ipolicyDiskTemplates = fromMaybe fdiskTemplates
                                         pdiskTemplates
                }
  toPartial (FilledIPolicy { ipolicyMinMaxISpecs  = fminmax
                           , ipolicyStdSpec       = fstd
                           , ipolicySpindleRatio  = fspindleRatio
                           , ipolicyMemoryRatio   = fmemoryRatio
                           , ipolicyVcpuRatio     = fvcpuRatio
                           , ipolicyDiskTemplates = fdiskTemplates}) =
    PartialIPolicy
                { ipolicyMinMaxISpecsP  = Just fminmax
                , ipolicyStdSpecP       = Just $ toPartial fstd
                , ipolicySpindleRatioP  = Just fspindleRatio
                , ipolicyMemoryRatioP   = Just fmemoryRatio
                , ipolicyVcpuRatioP     = Just fvcpuRatio
                , ipolicyDiskTemplatesP = Just fdiskTemplates
                }
  toFilled (PartialIPolicy { ipolicyMinMaxISpecsP  = pminmax
                           , ipolicyStdSpecP       = pstd
                           , ipolicySpindleRatioP  = pspindleRatio
                           , ipolicyMemoryRatioP   = pmemoryRatio
                           , ipolicyVcpuRatioP     = pvcpuRatio
                           , ipolicyDiskTemplatesP = pdiskTemplates}) =
    FilledIPolicy <$> pminmax <*> (toFilled =<< pstd) <*> pspindleRatio
                  <*> pmemoryRatio <*> pvcpuRatio <*> pdiskTemplates

-- | Disk state parameters.
--
-- As according to the documentation this option is unused by Ganeti,
-- the content is just a 'JSValue'.
type DiskState = Container JSValue

-- * Node definitions

$(buildParam "ND" "ndp"
  [ simpleField "oob_program"   [t| String |]
  , simpleField "spindle_count" [t| Int    |]
  , simpleField "exclusive_storage" [t| Bool |]
  , simpleField "ovs"           [t| Bool |]
  , simpleField "ovs_name"       [t| String |]
  , simpleField "ovs_link"       [t| String |]
  , simpleField "ssh_port"      [t| Int |]
  , simpleField "cpu_speed"     [t| Double |]
  ])

$(buildObject "Node" "node" $
  [ simpleField "name"              [t| String          |]
  , simpleField "primary_ip"        [t| String          |]
  , simpleField "secondary_ip"      [t| String          |]
  , simpleField "master_candidate"  [t| Bool            |]
  , simpleField "offline"           [t| Bool            |]
  , simpleField "drained"           [t| Bool            |]
  , simpleField "group"             [t| String          |]
  , simpleField "master_capable"    [t| Bool            |]
  , simpleField "vm_capable"        [t| Bool            |]
  , simpleField "ndparams"          [t| PartialNDParams |]
  , simpleField "powered"           [t| Bool            |]
  , notSerializeDefaultField [| emptyContainer |] $
    simpleField "hv_state_static"   [t| PartialHvState  |]
  , notSerializeDefaultField [| emptyContainer |] $
    simpleField "disk_state_static" [t| DiskState       |]
  ]
  ++ timeStampFields
  ++ uuidFields
  ++ serialFields
  ++ tagsFields)

instance TimeStampObject Node where
  cTimeOf = nodeCtime
  mTimeOf = nodeMtime

instance UuidObject Node where
  uuidOf = UTF8.toString . nodeUuid

instance SerialNoObject Node where
  serialOf = nodeSerial

instance TagsObject Node where
  tagsOf = nodeTags

-- * NodeGroup definitions

-- | The cluster/group disk parameters type.
type GroupDiskParams = Container DiskParams

-- | A mapping from network UUIDs to nic params of the networks.
type Networks = Container PartialNicParams

$(buildObject "NodeGroup" "group" $
  [ simpleField "name"              [t| String |]
  , defaultField [| [] |] $ simpleField "members" [t| [String] |]
  , simpleField "ndparams"          [t| PartialNDParams |]
  , simpleField "alloc_policy"      [t| AllocPolicy     |]
  , simpleField "ipolicy"           [t| PartialIPolicy  |]
  , simpleField "diskparams"        [t| GroupDiskParams |]
  , simpleField "networks"          [t| Networks        |]
  , notSerializeDefaultField [| emptyContainer |] $
    simpleField "hv_state_static"   [t| PartialHvState  |]
  , notSerializeDefaultField [| emptyContainer |] $
    simpleField "disk_state_static" [t| DiskState       |]
  ]
  ++ timeStampFields
  ++ uuidFields
  ++ serialFields
  ++ tagsFields)

instance TimeStampObject NodeGroup where
  cTimeOf = groupCtime
  mTimeOf = groupMtime

instance UuidObject NodeGroup where
  uuidOf = UTF8.toString . groupUuid

instance SerialNoObject NodeGroup where
  serialOf = groupSerial

instance TagsObject NodeGroup where
  tagsOf = groupTags

-- * Job scheduler filtering definitions

-- | Actions that can be performed when a filter matches.
data FilterAction
  = Accept
  | Pause
  | Reject
  | Continue
  | RateLimit Int
  deriving (Eq, Ord, Show)

instance JSON FilterAction where
  showJSON fa = case fa of
    Accept      -> JSString (toJSString "ACCEPT")
    Pause       -> JSString (toJSString "PAUSE")
    Reject      -> JSString (toJSString "REJECT")
    Continue    -> JSString (toJSString "CONTINUE")
    RateLimit n -> JSArray [ JSString (toJSString "RATE_LIMIT")
                           , JSRational False (fromIntegral n)
                           ]
  readJSON v = case v of
    -- `FilterAction`s are case-sensitive.
    JSString s | fromJSString s == "ACCEPT"   -> return Accept
    JSString s | fromJSString s == "PAUSE"    -> return Pause
    JSString s | fromJSString s == "REJECT"   -> return Reject
    JSString s | fromJSString s == "CONTINUE" -> return Continue
    JSArray (JSString s : rest) | fromJSString s == "RATE_LIMIT" ->
      case rest of
        [JSRational False n] | denominator n == 1 && numerator n > 0 ->
          return . RateLimit . fromIntegral $ numerator n
        _ -> fail "RATE_LIMIT argument must be a positive integer"
    x -> fail $ "malformed FilterAction JSON: " ++ J.showJSValue x ""


data FilterPredicate
  = FPJobId (Filter FilterField)
  | FPOpCode (Filter FilterField)
  | FPReason (Filter FilterField)
  deriving (Eq, Ord, Show)


instance JSON FilterPredicate where
  showJSON fp = case fp of
    FPJobId expr  -> JSArray [string "jobid",  showJSON expr]
    FPOpCode expr -> JSArray [string "opcode", showJSON expr]
    FPReason expr -> JSArray [string "reason", showJSON expr]
    where
      string = JSString . toJSString

  readJSON v = case v of
    -- Predicate names are case-sensitive.
    JSArray [JSString name, expr]
      | name == toJSString "jobid"  -> FPJobId <$> readJSON expr
      | name == toJSString "opcode" -> FPOpCode <$> readJSON expr
      | name == toJSString "reason" -> FPReason <$> readJSON expr
    JSArray (JSString name:params) ->
      fail $ "malformed FilterPredicate: bad parameter list for\
             \ '" ++ fromJSString name ++ "' predicate: "
             ++ J.showJSArray params ""
    _ -> fail "malformed FilterPredicate: must be a list with the first\
              \ entry being a string describing the predicate type"


$(buildObject "FilterRule" "fr" $
  [ simpleField "watermark"    [t| JobId             |]
  , simpleField "priority"     [t| NonNegative Int   |]
  , simpleField "predicates"   [t| [FilterPredicate] |]
  , simpleField "action"       [t| FilterAction      |]
  , simpleField "reason_trail" [t| ReasonTrail       |]
  ]
  ++ uuidFields)

instance UuidObject FilterRule where
  uuidOf = UTF8.toString . frUuid


-- | Order in which filter rules are evaluated, according to
-- `doc/design-optables.rst`.
-- For `FilterRule` fields not specified as important for the order,
-- we choose an arbitrary ordering effect (after the ones from the spec).
--
-- The `Ord` instance for `FilterRule` agrees with this function.
-- Yet it is recommended to use this function instead of `compare` to be
-- explicit that the spec order is used.
filterRuleOrder :: FilterRule -> FilterRule -> Ordering
filterRuleOrder = compare


instance Ord FilterRule where
  -- It is important that the Ord instance respects the ordering given in
  -- `doc/design-optables.rst` for the fields defined in there. The other
  -- fields may be ordered arbitrarily.
  -- Use `filterRuleOrder` when relying on the spec order.
  compare =
    comparing $ \(FilterRule watermark prio predicates action reason uuid) ->
      ( prio, watermark, uuid -- spec part
      , predicates, action, reason -- arbitrary part
      )


-- | IP family type
$(declareIADT "IpFamily"
  [ ("IpFamilyV4", 'AutoConf.pyAfInet4)
  , ("IpFamilyV6", 'AutoConf.pyAfInet6)
  ])
$(makeJSONInstance ''IpFamily)

-- | Conversion from IP family to IP version. This is needed because
-- Python uses both, depending on context.
ipFamilyToVersion :: IpFamily -> Int
ipFamilyToVersion IpFamilyV4 = C.ip4Version
ipFamilyToVersion IpFamilyV6 = C.ip6Version

-- | Cluster HvParams (hvtype to hvparams mapping).
type ClusterHvParams = GenericContainer Hypervisor HvParams

-- | Cluster Os-HvParams (os to hvparams mapping).
type OsHvParams = Container ClusterHvParams

-- | Cluser BeParams.
type ClusterBeParams = Container FilledBeParams

-- | Cluster OsParams.
type ClusterOsParams = Container OsParams
type ClusterOsParamsPrivate = Container (Private OsParams)

-- | Cluster NicParams.
type ClusterNicParams = Container FilledNicParams

-- | A low-high UID ranges.
type UidRange = (Int, Int)

formatUidRange :: UidRange -> String
formatUidRange (lower, higher)
  | lower == higher = show lower
  | otherwise       = show lower ++ "-" ++ show higher

-- | Cluster UID Pool, list (low, high) UID ranges.
type UidPool = [UidRange]

-- | The iallocator parameters type.
type IAllocatorParams = Container JSValue

-- | The master candidate client certificate digests
type CandidateCertificates = Container String

-- * Cluster definitions
$(buildObject "Cluster" "cluster" $
  [ simpleField "rsahostkeypub"                  [t| String                  |]
  , optionalField $
    simpleField "dsahostkeypub"                  [t| String                  |]
  , simpleField "highest_used_port"              [t| Int                     |]
  , simpleField "tcpudp_port_pool"               [t| [Int]                   |]
  , simpleField "mac_prefix"                     [t| String                  |]
  , optionalField $
    simpleField "volume_group_name"              [t| String                  |]
  , simpleField "reserved_lvs"                   [t| [String]                |]
  , optionalField $
    simpleField "drbd_usermode_helper"           [t| String                  |]
  , simpleField "master_node"                    [t| String                  |]
  , simpleField "master_ip"                      [t| String                  |]
  , simpleField "master_netdev"                  [t| String                  |]
  , simpleField "master_netmask"                 [t| Int                     |]
  , simpleField "use_external_mip_script"        [t| Bool                    |]
  , simpleField "cluster_name"                   [t| String                  |]
  , simpleField "file_storage_dir"               [t| String                  |]
  , simpleField "shared_file_storage_dir"        [t| String                  |]
  , simpleField "gluster_storage_dir"            [t| String                  |]
  , simpleField "enabled_hypervisors"            [t| [Hypervisor]            |]
  , simpleField "hvparams"                       [t| ClusterHvParams         |]
  , simpleField "os_hvp"                         [t| OsHvParams              |]
  , simpleField "beparams"                       [t| ClusterBeParams         |]
  , simpleField "osparams"                       [t| ClusterOsParams         |]
  , simpleField "osparams_private_cluster"       [t| ClusterOsParamsPrivate  |]
  , simpleField "nicparams"                      [t| ClusterNicParams        |]
  , simpleField "ndparams"                       [t| FilledNDParams          |]
  , simpleField "diskparams"                     [t| GroupDiskParams         |]
  , simpleField "candidate_pool_size"            [t| Int                     |]
  , simpleField "modify_etc_hosts"               [t| Bool                    |]
  , simpleField "modify_ssh_setup"               [t| Bool                    |]
  , simpleField "maintain_node_health"           [t| Bool                    |]
  , simpleField "uid_pool"                       [t| UidPool                 |]
  , simpleField "default_iallocator"             [t| String                  |]
  , simpleField "default_iallocator_params"      [t| IAllocatorParams        |]
  , simpleField "hidden_os"                      [t| [String]                |]
  , simpleField "blacklisted_os"                 [t| [String]                |]
  , simpleField "primary_ip_family"              [t| IpFamily                |]
  , simpleField "prealloc_wipe_disks"            [t| Bool                    |]
  , simpleField "ipolicy"                        [t| FilledIPolicy           |]
  , notSerializeDefaultField [| emptyContainer |] $
    simpleField "hv_state_static"                [t| FilledHvState           |]
  , notSerializeDefaultField [| emptyContainer |] $
    simpleField "disk_state_static"              [t| DiskState               |]
  , simpleField "enabled_disk_templates"         [t| [DiskTemplate]          |]
  , simpleField "candidate_certs"                [t| CandidateCertificates   |]
  , simpleField "max_running_jobs"               [t| Int                     |]
  , simpleField "max_tracked_jobs"               [t| Int                     |]
  , simpleField "install_image"                  [t| String                  |]
  , simpleField "instance_communication_network" [t| String                  |]
  , simpleField "zeroing_image"                  [t| String                  |]
  , simpleField "compression_tools"              [t| [String]                |]
  , simpleField "enabled_user_shutdown"          [t| Bool                    |]
  , simpleField "data_collectors"         [t| Container DataCollectorConfig  |]
  , defaultField [| [] |] $ simpleField
      "diagnose_data_collector_filename"         [t| String                  |]
  , simpleField "ssh_key_type"                   [t| SshKeyType              |]
  , simpleField "ssh_key_bits"                   [t| Int                     |]
 ]
 ++ timeStampFields
 ++ uuidFields
 ++ serialFields
 ++ tagsFields)

instance TimeStampObject Cluster where
  cTimeOf = clusterCtime
  mTimeOf = clusterMtime

instance UuidObject Cluster where
  uuidOf = UTF8.toString . clusterUuid

instance SerialNoObject Cluster where
  serialOf = clusterSerial

instance TagsObject Cluster where
  tagsOf = clusterTags

-- * ConfigData definitions

$(buildObject "ConfigData" "config" $
--  timeStampFields ++
  [ simpleField "version"    [t| Int                 |]
  , simpleField "cluster"    [t| Cluster             |]
  , simpleField "nodes"      [t| Container Node      |]
  , simpleField "nodegroups" [t| Container NodeGroup |]
  , simpleField "instances"  [t| Container Instance  |]
  , simpleField "networks"   [t| Container Network   |]
  , simpleField "disks"      [t| Container Disk      |]
  , simpleField "filters"    [t| Container FilterRule |]
  , simpleField "maintenance" [t| MaintenanceData    |]
  ]
  ++ timeStampFields
  ++ serialFields)

instance SerialNoObject ConfigData where
  serialOf = configSerial

instance TimeStampObject ConfigData where
  cTimeOf = configCtime
  mTimeOf = configMtime

-- * Master network parameters

$(buildObject "MasterNetworkParameters" "masterNetworkParameters"
  [ simpleField "uuid"      [t| String   |]
  , simpleField "ip"        [t| String   |]
  , simpleField "netmask"   [t| Int      |]
  , simpleField "netdev"    [t| String   |]
  , simpleField "ip_family" [t| IpFamily |]
  ])