{-# LANGUAGE TemplateHaskell, StandaloneDeriving #-}

{-| Implementation of opcodes parameters.

These are defined in a separate module only due to TemplateHaskell
stage restrictions - expressions defined in the current module can't
be passed to splices. So we have to either parameters/repeat each
parameter definition multiple times, or separate them into this
module.

-}

{-

Copyright (C) 2012, 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.OpParams
  ( ReplaceDisksMode(..)
  , DiskIndex
  , mkDiskIndex
  , unDiskIndex
  , DiskAccess(..)
  , INicParams(..)
  , IDiskParams(..)
  , RecreateDisksInfo(..)
  , DdmOldChanges(..)
  , SetParamsMods(..)
  , ExportTarget(..)
  , pInstanceName
  , pInstallImage
  , pInstanceCommunication
  , pOptInstanceCommunication
  , pInstanceUuid
  , pInstances
  , pName
  , pTagsList
  , pTagsObject
  , pTagsName
  , pOutputFields
  , pShutdownTimeout
  , pShutdownTimeout'
  , pShutdownInstance
  , pForce
  , pIgnoreOfflineNodes
  , pIgnoreHVVersions
  , pNodeName
  , pNodeUuid
  , pNodeNames
  , pNodeUuids
  , pGroupName
  , pMigrationMode
  , pMigrationLive
  , pMigrationCleanup
  , pForceVariant
  , pWaitForSync
  , pWaitForSyncFalse
  , pIgnoreConsistency
  , pStorageName
  , pUseLocking
  , pOpportunisticLocking
  , pNameCheck
  , pNodeGroupAllocPolicy
  , pGroupNodeParams
  , pQueryWhat
  , pEarlyRelease
  , pIpCheck
  , pIpConflictsCheck
  , pNoRemember
  , pMigrationTargetNode
  , pMigrationTargetNodeUuid
  , pMoveTargetNode
  , pMoveTargetNodeUuid
  , pMoveCompress
  , pBackupCompress
  , pStartupPaused
  , pVerbose
  , pDebug
  , pDebugSimulateErrors
  , pErrorCodes
  , pSkipChecks
  , pIgnoreErrors
  , pOptGroupName
  , pGroupDiskParams
  , pHvState
  , pDiskState
  , pIgnoreIpolicy
  , pHotplug
  , pHotplugIfPossible
  , pAllowRuntimeChgs
  , pInstDisks
  , pDiskTemplate
  , pOptDiskTemplate
  , pExtParams
  , pFileDriver
  , pFileStorageDir
  , pClusterFileStorageDir
  , pClusterSharedFileStorageDir
  , pClusterGlusterStorageDir
  , pInstanceCommunicationNetwork
  , pZeroingImage
  , pCompressionTools
  , pVgName
  , pEnabledHypervisors
  , pHypervisor
  , pClusterHvParams
  , pInstHvParams
  , pClusterBeParams
  , pInstBeParams
  , pResetDefaults
  , pOsHvp
  , pClusterOsParams
  , pClusterOsParamsPrivate
  , pInstOsParams
  , pInstOsParamsPrivate
  , pInstOsParamsSecret
  , pCandidatePoolSize
  , pMaxRunningJobs
  , pMaxTrackedJobs
  , pUidPool
  , pAddUids
  , pRemoveUids
  , pMaintainNodeHealth
  , pModifyEtcHosts
  , pPreallocWipeDisks
  , pNicParams
  , pInstNics
  , pNdParams
  , pIpolicy
  , pDrbdHelper
  , pDefaultIAllocator
  , pDefaultIAllocatorParams
  , pMasterNetdev
  , pMasterNetmask
  , pReservedLvs
  , pHiddenOs
  , pBlacklistedOs
  , pUseExternalMipScript
  , pQueryFields
  , pQueryFilter
  , pQueryFieldsFields
  , pOobCommand
  , pOobTimeout
  , pIgnoreStatus
  , pPowerDelay
  , pPrimaryIp
  , pSecondaryIp
  , pReadd
  , pNodeGroup
  , pMasterCapable
  , pVmCapable
  , pNames
  , pNodes
  , pRequiredNodes
  , pRequiredNodeUuids
  , pStorageType
  , pOptStorageType
  , pStorageChanges
  , pMasterCandidate
  , pOffline
  , pDrained
  , pAutoPromote
  , pPowered
  , pIallocator
  , pRemoteNode
  , pRemoteNodeUuid
  , pEvacMode
  , pIgnoreSoftErrors
  , pInstCreateMode
  , pNoInstall
  , pInstOs
  , pPrimaryNode
  , pPrimaryNodeUuid
  , pSecondaryNode
  , pSecondaryNodeUuid
  , pSourceHandshake
  , pSourceInstance
  , pSourceShutdownTimeout
  , pSourceX509Ca
  , pSrcNode
  , pSrcNodeUuid
  , pSrcPath
  , pStartInstance
  , pForthcoming
  , pCommit
  , pInstTags
  , pMultiAllocInstances
  , pTempOsParams
  , pTempOsParamsPrivate
  , pTempOsParamsSecret
  , pTempHvParams
  , pTempBeParams
  , pIgnoreFailures
  , pNewName
  , pIgnoreSecondaries
  , pRebootType
  , pIgnoreDiskSize
  , pRecreateDisksInfo
  , pStatic
  , pInstParamsNicChanges
  , pInstParamsDiskChanges
  , pRuntimeMem
  , pOsNameChange
  , pDiskIndex
  , pDiskChgAmount
  , pDiskChgAbsolute
  , pTargetGroups
  , pExportMode
  , pExportTargetNode
  , pExportTargetNodeUuid
  , pRemoveInstance
  , pIgnoreRemoveFailures
  , pX509KeyName
  , pX509DestCA
  , pZeroFreeSpace
  , pHelperStartupTimeout
  , pHelperShutdownTimeout
  , pZeroingTimeoutFixed
  , pZeroingTimeoutPerMiB
  , pTagSearchPattern
  , pRestrictedCommand
  , pReplaceDisksMode
  , pReplaceDisksList
  , pAllowFailover
  , pForceFailover
  , pDelayDuration
  , pDelayOnMaster
  , pDelayOnNodes
  , pDelayOnNodeUuids
  , pDelayRepeat
  , pDelayInterruptible
  , pDelayNoLocks
  , pIAllocatorDirection
  , pIAllocatorMode
  , pIAllocatorReqName
  , pIAllocatorNics
  , pIAllocatorDisks
  , pIAllocatorMemory
  , pIAllocatorVCpus
  , pIAllocatorOs
  , pIAllocatorInstances
  , pIAllocatorEvacMode
  , pIAllocatorSpindleUse
  , pIAllocatorCount
  , pJQueueNotifyWaitLock
  , pJQueueNotifyExec
  , pJQueueLogMessages
  , pJQueueFail
  , pTestDummyResult
  , pTestDummyMessages
  , pTestDummyFail
  , pTestDummySubmitJobs
  , pNetworkName
  , pNetworkAddress4
  , pNetworkGateway4
  , pNetworkAddress6
  , pNetworkGateway6
  , pNetworkMacPrefix
  , pNetworkAddRsvdIps
  , pNetworkRemoveRsvdIps
  , pNetworkMode
  , pNetworkLink
  , pNetworkVlan
  , pDryRun
  , pDebugLevel
  , pOpPriority
  , pDependencies
  , pComment
  , pReason
  , pSequential
  , pEnabledDiskTemplates
  , pEnabledUserShutdown
  , pAdminStateSource
  , pEnabledDataCollectors
  , pDataCollectorInterval
  , pNodeSslCerts
  , pSshKeyBits
  , pSshKeyType
  , pRenewSshKeys
  , pNodeSetup
  , pVerifyClutter
  , pLongSleep
  ) where

import Control.Monad (liftM, mplus)
import Text.JSON (JSON, JSValue(..), JSObject (..), readJSON, showJSON,
                  fromJSString, toJSObject)
import qualified Text.JSON
import Text.JSON.Pretty (pp_value)

import Ganeti.BasicTypes
import qualified Ganeti.Constants as C
import Ganeti.THH
import Ganeti.THH.Field
import Ganeti.Utils
import Ganeti.JSON
import Ganeti.Types
import qualified Ganeti.Query.Language as Qlang

-- * Helper functions and types

-- | Build a boolean field.
booleanField :: String -> Field
booleanField = flip simpleField [t| Bool |]

-- | Default a field to 'False'.
defaultFalse :: String -> Field
defaultFalse = defaultField [| False |] . booleanField

-- | Default a field to 'True'.
defaultTrue :: String -> Field
defaultTrue = defaultField [| True |] . booleanField

-- | An alias for a 'String' field.
stringField :: String -> Field
stringField = flip simpleField [t| String |]

-- | An alias for an optional string field.
optionalStringField :: String -> Field
optionalStringField = optionalField . stringField

-- | An alias for an optional non-empty string field.
optionalNEStringField :: String -> Field
optionalNEStringField = optionalField . flip simpleField [t| NonEmptyString |]

-- | Function to force a non-negative value, without returning via a
-- monad. This is needed for, and should be used /only/ in the case of
-- forcing constants. In case the constant is wrong (< 0), this will
-- become a runtime error.
forceNonNeg :: (Num a, Ord a, Show a) => a -> NonNegative a
forceNonNeg i = case mkNonNegative i of
                  Ok n -> n
                  Bad msg -> error msg

-- ** Disks

-- | Disk index type (embedding constraints on the index value via a
-- smart constructor).
newtype DiskIndex = DiskIndex { unDiskIndex :: Int }
  deriving (Show, Eq, Ord)

-- | Smart constructor for 'DiskIndex'.
mkDiskIndex :: (Monad m) => Int -> m DiskIndex
mkDiskIndex i | i >= 0 && i < C.maxDisks = return (DiskIndex i)
              | otherwise = fail $ "Invalid value for disk index '" ++
                            show i ++ "', required between 0 and " ++
                            show C.maxDisks

instance JSON DiskIndex where
  readJSON v = readJSON v >>= mkDiskIndex
  showJSON = showJSON . unDiskIndex

-- ** I* param types

-- | Type holding disk access modes.
$(declareSADT "DiskAccess"
  [ ("DiskReadOnly",  'C.diskRdonly)
  , ("DiskReadWrite", 'C.diskRdwr)
  ])
$(makeJSONInstance ''DiskAccess)

-- | NIC modification definition.
$(buildObject "INicParams" "inic"
  [ optionalField $ simpleField C.inicMac    [t| NonEmptyString |]
  , optionalField $ simpleField C.inicIp     [t| String         |]
  , optionalField $ simpleField C.inicMode   [t| NonEmptyString |]
  , optionalField $ simpleField C.inicLink   [t| NonEmptyString |]
  , optionalField $ simpleField C.inicName   [t| NonEmptyString |]
  , optionalField $ simpleField C.inicVlan   [t| String         |]
  , optionalField $ simpleField C.inicBridge [t| NonEmptyString |]
  , optionalField $ simpleField C.inicNetwork [t| NonEmptyString |]
  ])

deriving instance Ord INicParams

-- | Disk modification definition.
$(buildObject "IDiskParams" "idisk"
  [ specialNumericalField 'parseUnitAssumeBinary . optionalField
      $ simpleField C.idiskSize               [t| Int            |]
  , optionalField $ simpleField C.idiskMode   [t| DiskAccess     |]
  , optionalField $ simpleField C.idiskAdopt  [t| NonEmptyString |]
  , optionalField $ simpleField C.idiskVg     [t| NonEmptyString |]
  , optionalField $ simpleField C.idiskMetavg [t| NonEmptyString |]
  , optionalField $ simpleField C.idiskName   [t| NonEmptyString |]
  , optionalField $ simpleField C.idiskProvider [t| NonEmptyString |]
  , optionalField $ simpleField C.idiskSpindles [t| Int          |]
  , optionalField $ simpleField C.idiskAccess   [t| NonEmptyString |]
  , andRestArguments "opaque"
  ])

deriving instance Ord IDiskParams

-- | Disk changes type for OpInstanceRecreateDisks. This is a bit
-- strange, because the type in Python is something like Either
-- [DiskIndex] [DiskChanges], but we can't represent the type of an
-- empty list in JSON, so we have to add a custom case for the empty
-- list.
data RecreateDisksInfo
  = RecreateDisksAll
  | RecreateDisksIndices (NonEmpty DiskIndex)
  | RecreateDisksParams (NonEmpty (DiskIndex, IDiskParams))
    deriving (Eq, Show, Ord)

readRecreateDisks :: JSValue -> Text.JSON.Result RecreateDisksInfo
readRecreateDisks (JSArray []) = return RecreateDisksAll
readRecreateDisks v =
  case readJSON v::Text.JSON.Result [DiskIndex] of
    Text.JSON.Ok indices -> liftM RecreateDisksIndices (mkNonEmpty indices)
    _ -> case readJSON v::Text.JSON.Result [(DiskIndex, IDiskParams)] of
           Text.JSON.Ok params -> liftM RecreateDisksParams (mkNonEmpty params)
           _ -> fail $ "Can't parse disk information as either list of disk"
                ++ " indices or list of disk parameters; value received:"
                ++ show (pp_value v)

instance JSON RecreateDisksInfo where
  readJSON = readRecreateDisks
  showJSON  RecreateDisksAll            = showJSON ()
  showJSON (RecreateDisksIndices idx)   = showJSON idx
  showJSON (RecreateDisksParams params) = showJSON params

-- | Simple type for old-style ddm changes.
data DdmOldChanges = DdmOldIndex (NonNegative Int)
                   | DdmOldMod DdmSimple
                     deriving (Eq, Show, Ord)

readDdmOldChanges :: JSValue -> Text.JSON.Result DdmOldChanges
readDdmOldChanges v =
  case readJSON v::Text.JSON.Result (NonNegative Int) of
    Text.JSON.Ok nn -> return $ DdmOldIndex nn
    _ -> case readJSON v::Text.JSON.Result DdmSimple of
           Text.JSON.Ok ddms -> return $ DdmOldMod ddms
           _ -> fail $ "Can't parse value '" ++ show (pp_value v) ++ "' as"
                ++ " either index or modification"

instance JSON DdmOldChanges where
  showJSON (DdmOldIndex i) = showJSON i
  showJSON (DdmOldMod m)   = showJSON m
  readJSON = readDdmOldChanges

-- | Instance disk or nic modifications.
data SetParamsMods a
  = SetParamsEmpty
  | SetParamsDeprecated (NonEmpty (DdmOldChanges, a))
  | SetParamsNew (NonEmpty (DdmFull, Int, a))
  | SetParamsNewName (NonEmpty (DdmFull, String, a))
    deriving (Eq, Show, Ord)

-- | Custom deserialiser for 'SetParamsMods'.
readSetParams :: (JSON a) => JSValue -> Text.JSON.Result (SetParamsMods a)
readSetParams (JSArray []) = return SetParamsEmpty
readSetParams v =
  liftM SetParamsDeprecated (readJSON v)
  `mplus` liftM SetParamsNew (readJSON v)
  `mplus` liftM SetParamsNewName (readJSON v)

instance (JSON a) => JSON (SetParamsMods a) where
  showJSON SetParamsEmpty = showJSON ()
  showJSON (SetParamsDeprecated v) = showJSON v
  showJSON (SetParamsNew v) = showJSON v
  showJSON (SetParamsNewName v) = showJSON v
  readJSON = readSetParams

-- | Custom type for target_node parameter of OpBackupExport, which
-- varies depending on mode. FIXME: this uses an [JSValue] since
-- we don't care about individual rows (just like the Python code
-- tests). But the proper type could be parsed if we wanted.
data ExportTarget = ExportTargetLocal NonEmptyString
                  | ExportTargetRemote [JSValue]
                    deriving (Eq, Show, Ord)

-- | Custom reader for 'ExportTarget'.
readExportTarget :: JSValue -> Text.JSON.Result ExportTarget
readExportTarget (JSString s) = liftM ExportTargetLocal $
                                mkNonEmpty (fromJSString s)
readExportTarget (JSArray arr) = return $ ExportTargetRemote arr
readExportTarget v = fail $ "Invalid value received for 'target_node': " ++
                     show (pp_value v)

instance JSON ExportTarget where
  showJSON (ExportTargetLocal s)  = showJSON s
  showJSON (ExportTargetRemote l) = showJSON l
  readJSON = readExportTarget

-- * Common opcode parameters

pDryRun :: Field
pDryRun =
  withDoc "Run checks only, don't execute" .
  optionalField $ booleanField "dry_run"

pDebugLevel :: Field
pDebugLevel =
  withDoc "Debug level" .
  optionalField $ simpleField "debug_level" [t| NonNegative Int |]

pOpPriority :: Field
pOpPriority =
  withDoc "Opcode priority. Note: python uses a separate constant,\
          \ we're using the actual value we know it's the default" .
  defaultField [| OpPrioNormal |] $
  simpleField "priority" [t| OpSubmitPriority |]

pDependencies :: Field
pDependencies =
  withDoc "Job dependencies" .
  optionalNullSerField $ simpleField "depends" [t| [JobDependency] |]

pComment :: Field
pComment =
  withDoc "Comment field" .
  optionalNullSerField $ stringField "comment"

pReason :: Field
pReason =
  withDoc "Reason trail field" $
  simpleField C.opcodeReason [t| ReasonTrail |]

pSequential :: Field
pSequential =
  withDoc "Sequential job execution" $
  defaultFalse C.opcodeSequential

-- * Parameters

pDebugSimulateErrors :: Field
pDebugSimulateErrors =
  withDoc "Whether to simulate errors (useful for debugging)" $
  defaultFalse "debug_simulate_errors"

pErrorCodes :: Field
pErrorCodes =
  withDoc "Error codes" $
  defaultFalse "error_codes"

pSkipChecks :: Field
pSkipChecks =
  withDoc "Which checks to skip" .
  defaultField [| emptyListSet |] $
  simpleField "skip_checks" [t| ListSet VerifyOptionalChecks |]

pIgnoreErrors :: Field
pIgnoreErrors =
  withDoc "List of error codes that should be treated as warnings" .
  defaultField [| emptyListSet |] $
  simpleField "ignore_errors" [t| ListSet CVErrorCode |]

pVerbose :: Field
pVerbose =
  withDoc "Verbose mode" $
  defaultFalse "verbose"

pDebug :: Field
pDebug =
  withDoc "Debug mode" $
  defaultFalse "debug"

pOptGroupName :: Field
pOptGroupName =
  withDoc "Optional group name" .
  renameField "OptGroupName" .
  optionalField $ simpleField "group_name" [t| NonEmptyString |]

pGroupName :: Field
pGroupName =
  withDoc "Group name" $
  simpleField "group_name" [t| NonEmptyString |]

-- | Whether to hotplug device.
pHotplug :: Field
pHotplug = defaultFalse "hotplug"

pHotplugIfPossible :: Field
pHotplugIfPossible = defaultFalse "hotplug_if_possible"

pInstances :: Field
pInstances =
  withDoc "List of instances" .
  defaultField [| [] |] $
  simpleField "instances" [t| [NonEmptyString] |]

pOutputFields :: Field
pOutputFields =
  withDoc "Selected output fields" $
  simpleField "output_fields" [t| [NonEmptyString] |]

pName :: Field
pName =
  withDoc "A generic name" $
  simpleField "name" [t| NonEmptyString |]

pForce :: Field
pForce =
  withDoc "Whether to force the operation" $
  defaultFalse "force"

pHvState :: Field
pHvState =
  withDoc "Set hypervisor states" .
  optionalField $ simpleField "hv_state" [t| JSObject JSValue |]

pDiskState :: Field
pDiskState =
  withDoc "Set disk states" .
  optionalField $ simpleField "disk_state" [t| JSObject JSValue |]

-- | Cluster-wide default directory for storing file-backed disks.
pClusterFileStorageDir :: Field
pClusterFileStorageDir =
  renameField "ClusterFileStorageDir" $
  optionalStringField "file_storage_dir"

-- | Cluster-wide default directory for storing shared-file-backed disks.
pClusterSharedFileStorageDir :: Field
pClusterSharedFileStorageDir =
  renameField "ClusterSharedFileStorageDir" $
  optionalStringField "shared_file_storage_dir"

-- | Cluster-wide default directory for storing Gluster-backed disks.
pClusterGlusterStorageDir :: Field
pClusterGlusterStorageDir =
  renameField "ClusterGlusterStorageDir" $
  optionalStringField "gluster_storage_dir"

pInstallImage :: Field
pInstallImage =
  withDoc "OS image for running OS scripts in a safe environment" $
  optionalStringField "install_image"

pInstanceCommunicationNetwork :: Field
pInstanceCommunicationNetwork =
  optionalStringField "instance_communication_network"

-- | The OS to use when zeroing instance disks.
pZeroingImage :: Field
pZeroingImage =
  optionalStringField "zeroing_image"

-- | The additional tools that can be used to compress data in transit
pCompressionTools :: Field
pCompressionTools =
  withDoc "List of enabled compression tools" . optionalField $
  simpleField "compression_tools" [t| [NonEmptyString] |]

-- | Volume group name.
pVgName :: Field
pVgName =
  withDoc "Volume group name" $
  optionalStringField "vg_name"

pEnabledHypervisors :: Field
pEnabledHypervisors =
  withDoc "List of enabled hypervisors" .
  optionalField $
  simpleField "enabled_hypervisors" [t| [Hypervisor] |]

pClusterHvParams :: Field
pClusterHvParams =
  withDoc "Cluster-wide hypervisor parameters, hypervisor-dependent" .
  renameField "ClusterHvParams" .
  optionalField $
  simpleField "hvparams" [t| GenericContainer String (JSObject JSValue) |]

pClusterBeParams :: Field
pClusterBeParams =
  withDoc "Cluster-wide backend parameter defaults" .
  renameField "ClusterBeParams" .
  optionalField $ simpleField "beparams" [t| JSObject JSValue |]

pOsHvp :: Field
pOsHvp =
  withDoc "Cluster-wide per-OS hypervisor parameter defaults" .
  optionalField $
  simpleField "os_hvp" [t| GenericContainer String (JSObject JSValue) |]

pClusterOsParams :: Field
pClusterOsParams =
  withDoc "Cluster-wide OS parameter defaults" .
  renameField "ClusterOsParams" .
  optionalField $
  simpleField "osparams" [t| GenericContainer String (JSObject JSValue) |]

pClusterOsParamsPrivate :: Field
pClusterOsParamsPrivate =
  withDoc "Cluster-wide private OS parameter defaults" .
  renameField "ClusterOsParamsPrivate" .
  optionalField $
  -- This field needs an unique name to aid Python deserialization
  simpleField "osparams_private_cluster"
    [t| GenericContainer String (JSObject (Private JSValue)) |]

pGroupDiskParams :: Field
pGroupDiskParams =
  withDoc "Disk templates' parameter defaults" .
  optionalField $
  simpleField "diskparams"
              [t| GenericContainer DiskTemplate (JSObject JSValue) |]

pCandidatePoolSize :: Field
pCandidatePoolSize =
  withDoc "Master candidate pool size" .
  optionalField $ simpleField "candidate_pool_size" [t| Positive Int |]

pMaxRunningJobs :: Field
pMaxRunningJobs =
  withDoc "Maximal number of jobs to run simultaneously" .
  optionalField $ simpleField "max_running_jobs" [t| Positive Int |]

pMaxTrackedJobs :: Field
pMaxTrackedJobs =
  withDoc "Maximal number of jobs tracked in the job queue" .
  optionalField $ simpleField "max_tracked_jobs" [t| Positive Int |]


pUidPool :: Field
pUidPool =
  withDoc "Set UID pool, must be list of lists describing UID ranges\
          \ (two items, start and end inclusive)" .
  optionalField $ simpleField "uid_pool" [t| [(Int, Int)] |]

pAddUids :: Field
pAddUids =
  withDoc "Extend UID pool, must be list of lists describing UID\
          \ ranges (two items, start and end inclusive)" .
  optionalField $ simpleField "add_uids" [t| [(Int, Int)] |]

pRemoveUids :: Field
pRemoveUids =
  withDoc "Shrink UID pool, must be list of lists describing UID\
          \ ranges (two items, start and end inclusive) to be removed" .
  optionalField $ simpleField "remove_uids" [t| [(Int, Int)] |]

pMaintainNodeHealth :: Field
pMaintainNodeHealth =
  withDoc "Whether to automatically maintain node health" .
  optionalField $ booleanField "maintain_node_health"

-- | Whether to modify and keep in sync the @/etc/hosts@ files of nodes.
pModifyEtcHosts :: Field
pModifyEtcHosts = optionalField $ booleanField "modify_etc_hosts"

-- | Whether to wipe disks before allocating them to instances.
pPreallocWipeDisks :: Field
pPreallocWipeDisks =
  withDoc "Whether to wipe disks before allocating them to instances" .
  optionalField $ booleanField "prealloc_wipe_disks"

pNicParams :: Field
pNicParams =
  withDoc "Cluster-wide NIC parameter defaults" .
  optionalField $ simpleField "nicparams" [t| INicParams |]

pIpolicy :: Field
pIpolicy =
  withDoc "Ipolicy specs" .
  optionalField $ simpleField "ipolicy" [t| JSObject JSValue |]

pDrbdHelper :: Field
pDrbdHelper =
  withDoc "DRBD helper program" $
  optionalStringField "drbd_helper"

pDefaultIAllocator :: Field
pDefaultIAllocator =
  withDoc "Default iallocator for cluster" $
  optionalStringField "default_iallocator"

pDefaultIAllocatorParams :: Field
pDefaultIAllocatorParams =
  withDoc "Default iallocator parameters for cluster" . optionalField
    $ simpleField "default_iallocator_params" [t| JSObject JSValue |]

pMasterNetdev :: Field
pMasterNetdev =
  withDoc "Master network device" $
  optionalStringField "master_netdev"

pMasterNetmask :: Field
pMasterNetmask =
  withDoc "Netmask of the master IP" .
  optionalField $ simpleField "master_netmask" [t| NonNegative Int |]

pReservedLvs :: Field
pReservedLvs =
  withDoc "List of reserved LVs" .
  optionalField $ simpleField "reserved_lvs" [t| [NonEmptyString] |]

pHiddenOs :: Field
pHiddenOs =
  withDoc "Modify list of hidden operating systems: each modification\
          \ must have two items, the operation and the OS name; the operation\
          \ can be add or remove" .
  optionalField $ simpleField "hidden_os" [t| [(DdmSimple, NonEmptyString)] |]

pBlacklistedOs :: Field
pBlacklistedOs =
  withDoc "Modify list of blacklisted operating systems: each\
          \ modification must have two items, the operation and the OS name;\
          \ the operation can be add or remove" .
  optionalField $
  simpleField "blacklisted_os" [t| [(DdmSimple, NonEmptyString)] |]

pUseExternalMipScript :: Field
pUseExternalMipScript =
  withDoc "Whether to use an external master IP address setup script" .
  optionalField $ booleanField "use_external_mip_script"

pEnabledDiskTemplates :: Field
pEnabledDiskTemplates =
  withDoc "List of enabled disk templates" .
  optionalField $
  simpleField "enabled_disk_templates" [t| [DiskTemplate] |]

pEnabledUserShutdown :: Field
pEnabledUserShutdown =
  withDoc "Whether user shutdown is enabled cluster wide" .
  optionalField $
  simpleField "enabled_user_shutdown" [t| Bool |]

pQueryWhat :: Field
pQueryWhat =
  withDoc "Resource(s) to query for" $
  simpleField "what" [t| Qlang.QueryTypeOp |]

pUseLocking :: Field
pUseLocking =
  withDoc "Whether to use synchronization" $
  defaultFalse "use_locking"

pQueryFields :: Field
pQueryFields =
  withDoc "Requested fields" $
  simpleField "fields" [t| [NonEmptyString] |]

pQueryFilter :: Field
pQueryFilter =
  withDoc "Query filter" .
  optionalField $ simpleField "qfilter" [t| [JSValue] |]

pQueryFieldsFields :: Field
pQueryFieldsFields =
  withDoc "Requested fields; if not given, all are returned" .
  renameField "QueryFieldsFields" $
  optionalField pQueryFields

pNodeNames :: Field
pNodeNames =
  withDoc "List of node names to run the OOB command against" .
  defaultField [| [] |] $ simpleField "node_names" [t| [NonEmptyString] |]

pNodeUuids :: Field
pNodeUuids =
  withDoc "List of node UUIDs" .
  optionalField $ simpleField "node_uuids" [t| [NonEmptyString] |]

pOobCommand :: Field
pOobCommand =
  withDoc "OOB command to run" .
  renameField "OobCommand" $ simpleField "command" [t| OobCommand |]

pOobTimeout :: Field
pOobTimeout =
  withDoc "Timeout before the OOB helper will be terminated" .
  defaultField [| C.oobTimeout |] .
  renameField "OobTimeout" $ simpleField "timeout" [t| Int |]

pIgnoreStatus :: Field
pIgnoreStatus =
  withDoc "Ignores the node offline status for power off" $
  defaultFalse "ignore_status"

pPowerDelay :: Field
pPowerDelay =
  -- FIXME: we can't use the proper type "NonNegative Double", since
  -- the default constant is a plain Double, not a non-negative one.
  -- And trying to fix the constant introduces a cyclic import.
  withDoc "Time in seconds to wait between powering on nodes" .
  defaultField [| C.oobPowerDelay |] $
  simpleField "power_delay" [t| Double |]

pRequiredNodes :: Field
pRequiredNodes =
  withDoc "Required list of node names" .
  renameField "ReqNodes " $ simpleField "nodes" [t| [NonEmptyString] |]

pRequiredNodeUuids :: Field
pRequiredNodeUuids =
  withDoc "Required list of node UUIDs" .
  renameField "ReqNodeUuids " . optionalField $
  simpleField "node_uuids" [t| [NonEmptyString] |]

pRestrictedCommand :: Field
pRestrictedCommand =
  withDoc "Restricted command name" .
  renameField "RestrictedCommand" $
  simpleField "command" [t| NonEmptyString |]

pNodeName :: Field
pNodeName =
  withDoc "A required node name (for single-node LUs)" $
  simpleField "node_name" [t| NonEmptyString |]

pNodeUuid :: Field
pNodeUuid =
  withDoc "A node UUID (for single-node LUs)" .
  optionalField $ simpleField "node_uuid" [t| NonEmptyString |]

pPrimaryIp :: Field
pPrimaryIp =
  withDoc "Primary IP address" .
  optionalField $
  simpleField "primary_ip" [t| NonEmptyString |]

pSecondaryIp :: Field
pSecondaryIp =
  withDoc "Secondary IP address" $
  optionalNEStringField "secondary_ip"

pReadd :: Field
pReadd =
  withDoc "Whether node is re-added to cluster" $
  defaultFalse "readd"

pNodeGroup :: Field
pNodeGroup =
  withDoc "Initial node group" $
  optionalNEStringField "group"

pMasterCapable :: Field
pMasterCapable =
  withDoc "Whether node can become master or master candidate" .
  optionalField $ booleanField "master_capable"

pVmCapable :: Field
pVmCapable =
  withDoc "Whether node can host instances" .
  optionalField $ booleanField "vm_capable"

pNdParams :: Field
pNdParams =
  withDoc "Node parameters" .
  renameField "genericNdParams" .
  optionalField $ simpleField "ndparams" [t| JSObject JSValue |]

pNames :: Field
pNames =
  withDoc "List of names" .
  defaultField [| [] |] $ simpleField "names" [t| [NonEmptyString] |]

pNodes :: Field
pNodes =
  withDoc "List of nodes" .
  defaultField [| [] |] $ simpleField "nodes" [t| [NonEmptyString] |]

pStorageType :: Field
pStorageType =
  withDoc "Storage type" $ simpleField "storage_type" [t| StorageType |]

pOptStorageType :: Field
pOptStorageType =
  withDoc "Storage type" .
  renameField "OptStorageType" .
  optionalField $ simpleField "storage_type" [t| StorageType |]

pStorageName :: Field
pStorageName =
  withDoc "Storage name" .
  renameField "StorageName" .
  optionalField $ simpleField "name" [t| NonEmptyString |]

pStorageChanges :: Field
pStorageChanges =
  withDoc "Requested storage changes" $
  simpleField "changes" [t| JSObject JSValue |]

pIgnoreConsistency :: Field
pIgnoreConsistency =
  withDoc "Whether to ignore disk consistency" $
  defaultFalse "ignore_consistency"

pIgnoreHVVersions :: Field
pIgnoreHVVersions =
  withDoc "Whether to ignore incompatible Hypervisor versions" $
  defaultFalse "ignore_hvversions"

pMasterCandidate :: Field
pMasterCandidate =
  withDoc "Whether the node should become a master candidate" .
  optionalField $ booleanField "master_candidate"

pOffline :: Field
pOffline =
  withDoc "Whether to mark the node or instance offline" .
  optionalField $ booleanField "offline"

pDrained ::Field
pDrained =
  withDoc "Whether to mark the node as drained" .
  optionalField $ booleanField "drained"

pAutoPromote :: Field
pAutoPromote =
  withDoc "Whether node(s) should be promoted to master candidate if\
          \ necessary" $
  defaultFalse "auto_promote"

pPowered :: Field
pPowered =
  withDoc "Whether the node should be marked as powered" .
  optionalField $ booleanField "powered"

pMigrationMode :: Field
pMigrationMode =
  withDoc "Migration type (live/non-live)" .
  renameField "MigrationMode" .
  optionalField $
  simpleField "mode" [t| MigrationMode |]

pMigrationLive :: Field
pMigrationLive =
  withDoc "Obsolete \'live\' migration mode (do not use)" .
  renameField "OldLiveMode" . optionalField $ booleanField "live"

pMigrationTargetNode :: Field
pMigrationTargetNode =
  withDoc "Target node for instance migration/failover" $
  optionalNEStringField "target_node"

pMigrationTargetNodeUuid :: Field
pMigrationTargetNodeUuid =
  withDoc "Target node UUID for instance migration/failover" $
  optionalNEStringField "target_node_uuid"

pAllowRuntimeChgs :: Field
pAllowRuntimeChgs =
  withDoc "Whether to allow runtime changes while migrating" $
  defaultTrue "allow_runtime_changes"

pIgnoreIpolicy :: Field
pIgnoreIpolicy =
  withDoc "Whether to ignore ipolicy violations" $
  defaultFalse "ignore_ipolicy"

pIallocator :: Field
pIallocator =
  withDoc "Iallocator for deciding the target node for shared-storage\
          \ instances" $
  optionalNEStringField "iallocator"

pEarlyRelease :: Field
pEarlyRelease =
  withDoc "Whether to release locks as soon as possible" $
  defaultFalse "early_release"

pRemoteNode :: Field
pRemoteNode =
  withDoc "New secondary node" $
  optionalNEStringField "remote_node"

pRemoteNodeUuid :: Field
pRemoteNodeUuid =
  withDoc "New secondary node UUID" $
  optionalNEStringField "remote_node_uuid"

pEvacMode :: Field
pEvacMode =
  withDoc "Node evacuation mode" .
  renameField "EvacMode" $ simpleField "mode" [t| EvacMode |]

pIgnoreSoftErrors :: Field
pIgnoreSoftErrors =
  withDoc "Ignore soft htools errors" .
  optionalField $
  booleanField "ignore_soft_errors"

pInstanceName :: Field
pInstanceName =
  withDoc "A required instance name (for single-instance LUs)" $
  simpleField "instance_name" [t| String |]

pInstanceCommunication :: Field
pInstanceCommunication =
  withDoc C.instanceCommunicationDoc $
  defaultFalse "instance_communication"

pOptInstanceCommunication :: Field
pOptInstanceCommunication =
  withDoc C.instanceCommunicationDoc .
  renameField "OptInstanceCommunication" .
  optionalField $
  booleanField "instance_communication"

pForceVariant :: Field
pForceVariant =
  withDoc "Whether to force an unknown OS variant" $
  defaultFalse "force_variant"

pWaitForSync :: Field
pWaitForSync =
  withDoc "Whether to wait for the disk to synchronize" $
  defaultTrue "wait_for_sync"

pNameCheck :: Field
pNameCheck =
  withDoc "Whether to check name" $
  defaultTrue "name_check"

pInstBeParams :: Field
pInstBeParams =
  withDoc "Backend parameters for instance" .
  renameField "InstBeParams" .
  defaultField [| toJSObject [] |] $
  simpleField "beparams" [t| JSObject JSValue |]

pInstDisks :: Field
pInstDisks =
  withDoc "List of instance disks" .
  renameField "instDisks" $ simpleField "disks" [t| [IDiskParams] |]

pDiskTemplate :: Field
pDiskTemplate =
  withDoc "Disk template" $
  simpleField "disk_template" [t| DiskTemplate |]

pExtParams :: Field
pExtParams =
  withDoc "List of ExtStorage parameters" .
  renameField "InstExtParams" .
  defaultField [| toJSObject [] |] $
  simpleField "ext_params" [t| JSObject JSValue |]

pFileDriver :: Field
pFileDriver =
  withDoc "Driver for file-backed disks" .
  optionalField $ simpleField "file_driver" [t| FileDriver |]

pFileStorageDir :: Field
pFileStorageDir =
  withDoc "Directory for storing file-backed disks" $
  optionalNEStringField "file_storage_dir"

pInstHvParams :: Field
pInstHvParams =
  withDoc "Hypervisor parameters for instance, hypervisor-dependent" .
  renameField "InstHvParams" .
  defaultField [| toJSObject [] |] $
  simpleField "hvparams" [t| JSObject JSValue |]

pHypervisor :: Field
pHypervisor =
  withDoc "Selected hypervisor for an instance" .
  optionalField $
  simpleField "hypervisor" [t| Hypervisor |]

pResetDefaults :: Field
pResetDefaults =
  withDoc "Reset instance parameters to default if equal" $
  defaultFalse "identify_defaults"

pIpCheck :: Field
pIpCheck =
  withDoc "Whether to ensure instance's IP address is inactive" $
  defaultTrue "ip_check"

pIpConflictsCheck :: Field
pIpConflictsCheck =
  withDoc "Whether to check for conflicting IP addresses" $
  defaultTrue "conflicts_check"

pInstCreateMode :: Field
pInstCreateMode =
  withDoc "Instance creation mode" .
  renameField "InstCreateMode" $ simpleField "mode" [t| InstCreateMode |]

pInstNics :: Field
pInstNics =
  withDoc "List of NIC (network interface) definitions" $
  simpleField "nics" [t| [INicParams] |]

pNoInstall :: Field
pNoInstall =
  withDoc "Do not install the OS (will disable automatic start)" .
  optionalField $ booleanField "no_install"

pInstOs :: Field
pInstOs =
  withDoc "OS type for instance installation" $
  optionalNEStringField "os_type"

pInstOsParams :: Field
pInstOsParams =
  withDoc "OS parameters for instance" .
  renameField "InstOsParams" .
  defaultField [| toJSObject [] |] $
  simpleField "osparams" [t| JSObject JSValue |]

pInstOsParamsPrivate :: Field
pInstOsParamsPrivate =
  withDoc "Private OS parameters for instance" .
  optionalField $
  simpleField "osparams_private" [t| JSObject (Private JSValue) |]

pInstOsParamsSecret :: Field
pInstOsParamsSecret =
  withDoc "Secret OS parameters for instance" .
  optionalField $
  simpleField "osparams_secret" [t| JSObject (Secret JSValue) |]

pPrimaryNode :: Field
pPrimaryNode =
  withDoc "Primary node for an instance" $
  optionalNEStringField "pnode"

pPrimaryNodeUuid :: Field
pPrimaryNodeUuid =
  withDoc "Primary node UUID for an instance" $
  optionalNEStringField "pnode_uuid"

pSecondaryNode :: Field
pSecondaryNode =
  withDoc "Secondary node for an instance" $
  optionalNEStringField "snode"

pSecondaryNodeUuid :: Field
pSecondaryNodeUuid =
  withDoc "Secondary node UUID for an instance" $
  optionalNEStringField "snode_uuid"

pSourceHandshake :: Field
pSourceHandshake =
  withDoc "Signed handshake from source (remote import only)" .
  optionalField $ simpleField "source_handshake" [t| [JSValue] |]

pSourceInstance :: Field
pSourceInstance =
  withDoc "Source instance name (remote import only)" $
  optionalNEStringField "source_instance_name"

-- FIXME: non-negative int, whereas the constant is a plain int.
pSourceShutdownTimeout :: Field
pSourceShutdownTimeout =
  withDoc "How long source instance was given to shut down (remote import\
          \ only)" .
  defaultField [| forceNonNeg C.defaultShutdownTimeout |] $
  simpleField "source_shutdown_timeout" [t| NonNegative Int |]

pSourceX509Ca :: Field
pSourceX509Ca =
  withDoc "Source X509 CA in PEM format (remote import only)" $
  optionalNEStringField "source_x509_ca"

pSrcNode :: Field
pSrcNode =
  withDoc "Source node for import" $
  optionalNEStringField "src_node"

pSrcNodeUuid :: Field
pSrcNodeUuid =
  withDoc "Source node UUID for import" $
  optionalNEStringField "src_node_uuid"

pSrcPath :: Field
pSrcPath =
  withDoc "Source directory for import" $
  optionalNEStringField "src_path"

pStartInstance :: Field
pStartInstance =
  withDoc "Whether to start instance after creation" $
  defaultTrue "start"

pForthcoming :: Field
pForthcoming =
  withDoc "Whether to only reserve resources" $
  defaultFalse "forthcoming"

pCommit :: Field
pCommit =
  withDoc "Commit the already reserved instance" $
  defaultFalse "commit"

-- FIXME: unify/simplify with pTags, once that migrates to NonEmpty String"
pInstTags :: Field
pInstTags =
  withDoc "Instance tags" .
  renameField "InstTags" .
  defaultField [| [] |] $
  simpleField "tags" [t| [NonEmptyString] |]

pMultiAllocInstances :: Field
pMultiAllocInstances =
  withDoc "List of instance create opcodes describing the instances to\
          \ allocate" .
  renameField "InstMultiAlloc" .
  defaultField [| [] |] $
  simpleField "instances"[t| [JSValue] |]

pOpportunisticLocking :: Field
pOpportunisticLocking =
  withDoc "Whether to employ opportunistic locking for nodes, meaning\
          \ nodes already locked by another opcode won't be considered for\
          \ instance allocation (only when an iallocator is used)" $
  defaultFalse "opportunistic_locking"

pInstanceUuid :: Field
pInstanceUuid =
  withDoc "An instance UUID (for single-instance LUs)" .
  optionalField $ simpleField "instance_uuid" [t| NonEmptyString |]

pTempOsParams :: Field
pTempOsParams =
  withDoc "Temporary OS parameters (currently only in reinstall, might be\
          \ added to install as well)" .
  renameField "TempOsParams" .
  optionalField $ simpleField "osparams" [t| JSObject JSValue |]

pTempOsParamsPrivate :: Field
pTempOsParamsPrivate =
  withDoc "Private OS parameters for instance reinstalls" .
  optionalField $
  simpleField "osparams_private" [t| JSObject (Private JSValue) |]

pTempOsParamsSecret :: Field
pTempOsParamsSecret =
  withDoc "Secret OS parameters for instance reinstalls" .
  optionalField $
  simpleField "osparams_secret" [t| JSObject (Secret JSValue) |]

pShutdownTimeout :: Field
pShutdownTimeout =
  withDoc "How long to wait for instance to shut down" .
  defaultField [| forceNonNeg C.defaultShutdownTimeout |] $
  simpleField "shutdown_timeout" [t| NonNegative Int |]

-- | Another name for the shutdown timeout, because we like to be
-- inconsistent.
pShutdownTimeout' :: Field
pShutdownTimeout' =
  withDoc "How long to wait for instance to shut down" .
  renameField "InstShutdownTimeout" .
  defaultField [| forceNonNeg C.defaultShutdownTimeout |] $
  simpleField "timeout" [t| NonNegative Int |]

pIgnoreFailures :: Field
pIgnoreFailures =
  withDoc "Whether to ignore failures during removal" $
  defaultFalse "ignore_failures"

pNewName :: Field
pNewName =
  withDoc "New group or instance name" $
  simpleField "new_name" [t| NonEmptyString |]

pIgnoreOfflineNodes :: Field
pIgnoreOfflineNodes =
  withDoc "Whether to ignore offline nodes" $
  defaultFalse "ignore_offline_nodes"

pTempHvParams :: Field
pTempHvParams =
  withDoc "Temporary hypervisor parameters, hypervisor-dependent" .
  renameField "TempHvParams" .
  defaultField [| toJSObject [] |] $
  simpleField "hvparams" [t| JSObject JSValue |]

pTempBeParams :: Field
pTempBeParams =
  withDoc "Temporary backend parameters" .
  renameField "TempBeParams" .
  defaultField [| toJSObject [] |] $
  simpleField "beparams" [t| JSObject JSValue |]

pNoRemember :: Field
pNoRemember =
  withDoc "Do not remember instance state changes" $
  defaultFalse "no_remember"

pStartupPaused :: Field
pStartupPaused =
  withDoc "Pause instance at startup" $
  defaultFalse "startup_paused"

pIgnoreSecondaries :: Field
pIgnoreSecondaries =
  withDoc "Whether to start the instance even if secondary disks are failing" $
  defaultFalse "ignore_secondaries"

pRebootType :: Field
pRebootType =
  withDoc "How to reboot the instance" $
  simpleField "reboot_type" [t| RebootType |]

pReplaceDisksMode :: Field
pReplaceDisksMode =
  withDoc "Replacement mode" .
  renameField "ReplaceDisksMode" $ simpleField "mode" [t| ReplaceDisksMode |]

pReplaceDisksList :: Field
pReplaceDisksList =
  withDoc "List of disk indices" .
  renameField "ReplaceDisksList" .
  defaultField [| [] |] $
  simpleField "disks" [t| [DiskIndex] |]

pMigrationCleanup :: Field
pMigrationCleanup =
  withDoc "Whether a previously failed migration should be cleaned up" .
  renameField "MigrationCleanup" $ defaultFalse "cleanup"

pAllowFailover :: Field
pAllowFailover =
  withDoc "Whether we can fallback to failover if migration is not possible" $
  defaultFalse "allow_failover"

pForceFailover :: Field
pForceFailover =
  withDoc "Disallow migration moves and always use failovers" $
  defaultFalse "force_failover"

pMoveTargetNode :: Field
pMoveTargetNode =
  withDoc "Target node for instance move" .
  renameField "MoveTargetNode" $
  simpleField "target_node" [t| NonEmptyString |]

pMoveTargetNodeUuid :: Field
pMoveTargetNodeUuid =
  withDoc "Target node UUID for instance move" .
  renameField "MoveTargetNodeUuid" . optionalField $
  simpleField "target_node_uuid" [t| NonEmptyString |]

pMoveCompress :: Field
pMoveCompress =
  withDoc "Compression mode to use during instance moves" .
  defaultField [| C.iecNone |] $
  simpleField "compress" [t| String |]

pBackupCompress :: Field
pBackupCompress =
  withDoc "Compression mode to use for moves during backups/imports" .
  defaultField [| C.iecNone |] $
  simpleField "compress" [t| String |]

pIgnoreDiskSize :: Field
pIgnoreDiskSize =
  withDoc "Whether to ignore recorded disk size" $
  defaultFalse "ignore_size"

pWaitForSyncFalse :: Field
pWaitForSyncFalse =
  withDoc "Whether to wait for the disk to synchronize (defaults to false)" $
  defaultField [| False |] pWaitForSync

pRecreateDisksInfo :: Field
pRecreateDisksInfo =
  withDoc "Disk list for recreate disks" .
  renameField "RecreateDisksInfo" .
  defaultField [| RecreateDisksAll |] $
  simpleField "disks" [t| RecreateDisksInfo |]

pStatic :: Field
pStatic =
  withDoc "Whether to only return configuration data without querying nodes" $
  defaultFalse "static"

pInstParamsNicChanges :: Field
pInstParamsNicChanges =
  withDoc "List of NIC changes" .
  renameField "InstNicChanges" .
  defaultField [| SetParamsEmpty |] $
  simpleField "nics" [t| SetParamsMods INicParams |]

pInstParamsDiskChanges :: Field
pInstParamsDiskChanges =
  withDoc "List of disk changes" .
  renameField "InstDiskChanges" .
  defaultField [| SetParamsEmpty |] $
  simpleField "disks" [t| SetParamsMods IDiskParams |]

pRuntimeMem :: Field
pRuntimeMem =
  withDoc "New runtime memory" .
  optionalField $ simpleField "runtime_mem" [t| Positive Int |]

pOptDiskTemplate :: Field
pOptDiskTemplate =
  withDoc "Instance disk template" .
  optionalField .
  renameField "OptDiskTemplate" $
  simpleField "disk_template" [t| DiskTemplate |]

pOsNameChange :: Field
pOsNameChange =
  withDoc "Change the instance's OS without reinstalling the instance" $
  optionalNEStringField "os_name"

pDiskIndex :: Field
pDiskIndex =
  withDoc "Disk index for e.g. grow disk" .
  renameField "DiskIndex " $ simpleField "disk" [t| DiskIndex |]

pDiskChgAmount :: Field
pDiskChgAmount =
  withDoc "Disk amount to add or grow to" .
  renameField "DiskChgAmount" $ simpleField "amount" [t| NonNegative Int |]

pDiskChgAbsolute :: Field
pDiskChgAbsolute =
  withDoc
    "Whether the amount parameter is an absolute target or a relative one" .
  renameField "DiskChkAbsolute" $ defaultFalse "absolute"

pTargetGroups :: Field
pTargetGroups =
  withDoc
    "Destination group names or UUIDs (defaults to \"all but current group\")" .
  optionalField $ simpleField "target_groups" [t| [NonEmptyString] |]

pNodeGroupAllocPolicy :: Field
pNodeGroupAllocPolicy =
  withDoc "Instance allocation policy" .
  optionalField $
  simpleField "alloc_policy" [t| AllocPolicy |]

pGroupNodeParams :: Field
pGroupNodeParams =
  withDoc "Default node parameters for group" .
  optionalField $ simpleField "ndparams" [t| JSObject JSValue |]

pExportMode :: Field
pExportMode =
  withDoc "Export mode" .
  renameField "ExportMode" $ simpleField "mode" [t| ExportMode |]

-- FIXME: Rename target_node as it changes meaning for different
-- export modes (e.g. "destination")
pExportTargetNode :: Field
pExportTargetNode =
  withDoc "Target node (depends on export mode)" .
  renameField "ExportTarget" $
  simpleField "target_node" [t| ExportTarget |]

pExportTargetNodeUuid :: Field
pExportTargetNodeUuid =
  withDoc "Target node UUID (if local export)" .
  renameField "ExportTargetNodeUuid" . optionalField $
  simpleField "target_node_uuid" [t| NonEmptyString |]

pShutdownInstance :: Field
pShutdownInstance =
  withDoc "Whether to shutdown the instance before export" $
  defaultTrue "shutdown"

pRemoveInstance :: Field
pRemoveInstance =
  withDoc "Whether to remove instance after export" $
  defaultFalse "remove_instance"

pIgnoreRemoveFailures :: Field
pIgnoreRemoveFailures =
  withDoc "Whether to ignore failures while removing instances" $
  defaultFalse "ignore_remove_failures"

pX509KeyName :: Field
pX509KeyName =
  withDoc "Name of X509 key (remote export only)" .
  optionalField $ simpleField "x509_key_name" [t| [JSValue] |]

pX509DestCA :: Field
pX509DestCA =
  withDoc "Destination X509 CA (remote export only)" $
  optionalNEStringField "destination_x509_ca"

pZeroFreeSpace :: Field
pZeroFreeSpace =
  withDoc "Whether to zero the free space on the disks of the instance" $
  defaultFalse "zero_free_space"

pHelperStartupTimeout :: Field
pHelperStartupTimeout =
  withDoc "Startup timeout for the helper VM" .
  optionalField $ simpleField "helper_startup_timeout" [t| Int |]

pHelperShutdownTimeout :: Field
pHelperShutdownTimeout =
  withDoc "Shutdown timeout for the helper VM" .
  optionalField $ simpleField "helper_shutdown_timeout" [t| Int |]

pZeroingTimeoutFixed :: Field
pZeroingTimeoutFixed =
  withDoc "The fixed part of time to wait before declaring the zeroing\
           \ operation to have failed" .
  optionalField $ simpleField "zeroing_timeout_fixed" [t| Int |]

pZeroingTimeoutPerMiB :: Field
pZeroingTimeoutPerMiB =
  withDoc "The variable part of time to wait before declaring the zeroing\
           \ operation to have failed, dependent on total size of disks" .
  optionalField $ simpleField "zeroing_timeout_per_mib" [t| Double |]

pTagsObject :: Field
pTagsObject =
  withDoc "Tag kind" $
  simpleField "kind" [t| TagKind |]

pTagsName :: Field
pTagsName =
  withDoc "Name of object" .
  renameField "TagsGetName" .
  optionalField $ simpleField "name" [t| String |]

pTagsList :: Field
pTagsList =
  withDoc "List of tag names" .
  renameField "TagsList" $
  simpleField "tags" [t| [String] |]

-- FIXME: this should be compiled at load time?
pTagSearchPattern :: Field
pTagSearchPattern =
  withDoc "Search pattern (regular expression)" .
  renameField "TagSearchPattern" $
  simpleField "pattern" [t| NonEmptyString |]

pDelayDuration :: Field
pDelayDuration =
  withDoc "Duration parameter for 'OpTestDelay'" .
  renameField "DelayDuration" $
  simpleField "duration" [t| Double |]

pDelayOnMaster :: Field
pDelayOnMaster =
  withDoc "on_master field for 'OpTestDelay'" .
  renameField "DelayOnMaster" $
  defaultTrue "on_master"

pDelayOnNodes :: Field
pDelayOnNodes =
  withDoc "on_nodes field for 'OpTestDelay'" .
  renameField "DelayOnNodes" .
  defaultField [| [] |] $
  simpleField "on_nodes" [t| [NonEmptyString] |]

pDelayOnNodeUuids :: Field
pDelayOnNodeUuids =
  withDoc "on_node_uuids field for 'OpTestDelay'" .
  renameField "DelayOnNodeUuids" . optionalField $
  simpleField "on_node_uuids" [t| [NonEmptyString] |]

pDelayRepeat :: Field
pDelayRepeat =
  withDoc "Repeat parameter for OpTestDelay" .
  renameField "DelayRepeat" .
  defaultField [| forceNonNeg (0::Int) |] $
  simpleField "repeat" [t| NonNegative Int |]

pDelayInterruptible :: Field
pDelayInterruptible =
  withDoc "Allows socket-based interruption of a running OpTestDelay" .
  renameField "DelayInterruptible" .
  defaultField [| False |] $
  simpleField "interruptible" [t| Bool |]

pDelayNoLocks :: Field
pDelayNoLocks =
  withDoc "Don't take locks during the delay" .
  renameField "DelayNoLocks" $
  defaultTrue "no_locks"

pIAllocatorDirection :: Field
pIAllocatorDirection =
  withDoc "IAllocator test direction" .
  renameField "IAllocatorDirection" $
  simpleField "direction" [t| IAllocatorTestDir |]

pIAllocatorMode :: Field
pIAllocatorMode =
  withDoc "IAllocator test mode" .
  renameField "IAllocatorMode" $
  simpleField "mode" [t| IAllocatorMode |]

pIAllocatorReqName :: Field
pIAllocatorReqName =
  withDoc "IAllocator target name (new instance, node to evac, etc.)" .
  renameField "IAllocatorReqName" $ simpleField "name" [t| NonEmptyString |]

pIAllocatorNics :: Field
pIAllocatorNics =
  withDoc "Custom OpTestIAllocator nics" .
  renameField "IAllocatorNics" .
  optionalField $ simpleField "nics" [t| [INicParams] |]

pIAllocatorDisks :: Field
pIAllocatorDisks =
  withDoc "Custom OpTestAllocator disks" .
  renameField "IAllocatorDisks" .
  optionalField $ simpleField "disks" [t| [JSValue] |]

pIAllocatorMemory :: Field
pIAllocatorMemory =
  withDoc "IAllocator memory field" .
  renameField "IAllocatorMem" .
  optionalField $
  simpleField "memory" [t| NonNegative Int |]

pIAllocatorVCpus :: Field
pIAllocatorVCpus =
  withDoc "IAllocator vcpus field" .
  renameField "IAllocatorVCpus" .
  optionalField $
  simpleField "vcpus" [t| NonNegative Int |]

pIAllocatorOs :: Field
pIAllocatorOs =
  withDoc "IAllocator os field" .
  renameField "IAllocatorOs" $ optionalNEStringField "os"

pIAllocatorInstances :: Field
pIAllocatorInstances =
  withDoc "IAllocator instances field" .
  renameField "IAllocatorInstances " .
  optionalField $
  simpleField "instances" [t| [NonEmptyString] |]

pIAllocatorEvacMode :: Field
pIAllocatorEvacMode =
  withDoc "IAllocator evac mode" .
  renameField "IAllocatorEvacMode" .
  optionalField $
  simpleField "evac_mode" [t| EvacMode |]

pIAllocatorSpindleUse :: Field
pIAllocatorSpindleUse =
  withDoc "IAllocator spindle use" .
  renameField "IAllocatorSpindleUse" .
  defaultField [| forceNonNeg (1::Int) |] $
  simpleField "spindle_use" [t| NonNegative Int |]

pIAllocatorCount :: Field
pIAllocatorCount =
  withDoc "IAllocator count field" .
  renameField "IAllocatorCount" .
  defaultField [| forceNonNeg (1::Int) |] $
  simpleField "count" [t| NonNegative Int |]

pJQueueNotifyWaitLock :: Field
pJQueueNotifyWaitLock =
  withDoc "'OpTestJqueue' notify_waitlock" $
  defaultFalse "notify_waitlock"

pJQueueNotifyExec :: Field
pJQueueNotifyExec =
  withDoc "'OpTestJQueue' notify_exec" $
  defaultFalse "notify_exec"

pJQueueLogMessages :: Field
pJQueueLogMessages =
  withDoc "'OpTestJQueue' log_messages" .
  defaultField [| [] |] $ simpleField "log_messages" [t| [String] |]

pJQueueFail :: Field
pJQueueFail =
  withDoc "'OpTestJQueue' fail attribute" .
  renameField "JQueueFail" $ defaultFalse "fail"

pTestDummyResult :: Field
pTestDummyResult =
  withDoc "'OpTestDummy' result field" .
  renameField "TestDummyResult" $ simpleField "result" [t| JSValue |]

pTestDummyMessages :: Field
pTestDummyMessages =
  withDoc "'OpTestDummy' messages field" .
  renameField "TestDummyMessages" $
  simpleField "messages" [t| JSValue |]

pTestDummyFail :: Field
pTestDummyFail =
  withDoc "'OpTestDummy' fail field" .
  renameField "TestDummyFail" $ simpleField "fail" [t| JSValue |]

pTestDummySubmitJobs :: Field
pTestDummySubmitJobs =
  withDoc "'OpTestDummy' submit_jobs field" .
  renameField "TestDummySubmitJobs" $
  simpleField "submit_jobs" [t| JSValue |]

pNetworkName :: Field
pNetworkName =
  withDoc "Network name" $
  simpleField "network_name" [t| NonEmptyString |]

pNetworkAddress4 :: Field
pNetworkAddress4 =
  withDoc "Network address (IPv4 subnet)" .
  renameField "NetworkAddress4" $
  simpleField "network" [t| IPv4Network |]

pNetworkGateway4 :: Field
pNetworkGateway4 =
  withDoc "Network gateway (IPv4 address)" .
  renameField "NetworkGateway4" .
  optionalField $ simpleField "gateway" [t| IPv4Address |]

pNetworkAddress6 :: Field
pNetworkAddress6 =
  withDoc "Network address (IPv6 subnet)" .
  renameField "NetworkAddress6" .
  optionalField $ simpleField "network6" [t| IPv6Network |]

pNetworkGateway6 :: Field
pNetworkGateway6 =
  withDoc "Network gateway (IPv6 address)" .
  renameField "NetworkGateway6" .
  optionalField $ simpleField "gateway6" [t| IPv6Address |]

pNetworkMacPrefix :: Field
pNetworkMacPrefix =
  withDoc "Network specific mac prefix (that overrides the cluster one)" .
  renameField "NetMacPrefix" $
  optionalNEStringField "mac_prefix"

pNetworkAddRsvdIps :: Field
pNetworkAddRsvdIps =
  withDoc "Which IP addresses to reserve" .
  renameField "NetworkAddRsvdIps" .
  optionalField $
  simpleField "add_reserved_ips" [t| [IPv4Address] |]

pNetworkRemoveRsvdIps :: Field
pNetworkRemoveRsvdIps =
  withDoc "Which external IP addresses to release" .
  renameField "NetworkRemoveRsvdIps" .
  optionalField $
  simpleField "remove_reserved_ips" [t| [IPv4Address] |]

pNetworkMode :: Field
pNetworkMode =
  withDoc "Network mode when connecting to a group" $
  simpleField "network_mode" [t| NICMode |]

pNetworkLink :: Field
pNetworkLink =
  withDoc "Network link when connecting to a group" $
  simpleField "network_link" [t| NonEmptyString |]

pAdminStateSource :: Field
pAdminStateSource =
  withDoc "Who last changed the instance admin state" .
  optionalField $
  simpleField "admin_state_source" [t| AdminStateSource |]

pNetworkVlan :: Field
pNetworkVlan =
  withDoc "Network vlan when connecting to a group" .
  defaultField [| "" |] $ stringField "network_vlan"

pEnabledDataCollectors :: Field
pEnabledDataCollectors =
  withDoc "Set the active data collectors" .
  optionalField $
  simpleField C.dataCollectorsEnabledName [t| GenericContainer String Bool |]

pDataCollectorInterval :: Field
pDataCollectorInterval =
  withDoc "Sets the interval in that data collectors are run" .
  optionalField $
  simpleField C.dataCollectorsIntervalName [t| GenericContainer String Int |]

pNodeSslCerts :: Field
pNodeSslCerts =
  withDoc "Whether to renew node SSL certificates" .
  defaultField [| False |] $
  simpleField "node_certificates" [t| Bool |]

pSshKeyBits :: Field
pSshKeyBits =
  withDoc "The number of bits of the SSH key Ganeti uses" .
  optionalField $ simpleField "ssh_key_bits" [t| Positive Int |]

pSshKeyType :: Field
pSshKeyType =
  withDoc "The type of the SSH key Ganeti uses" .
  optionalField $ simpleField "ssh_key_type" [t| SshKeyType |]

pRenewSshKeys :: Field
pRenewSshKeys =
  withDoc "Whether to renew SSH keys" .
  defaultField [| False |] $
  simpleField "renew_ssh_keys" [t| Bool |]

pNodeSetup :: Field
pNodeSetup =
  withDoc "Whether to perform a SSH setup on the new node" .
  defaultField [| False |] $
  simpleField "node_setup" [t| Bool |]

pVerifyClutter :: Field
pVerifyClutter =
  withDoc "Whether to check for clutter in the 'authorized_keys' file." .
  defaultField [| False |] $
  simpleField "verify_clutter" [t| Bool |]

pLongSleep :: Field
pLongSleep =
  withDoc "Whether to allow long instance shutdowns during exports" .
  defaultField [| False |] $
  simpleField "long_sleep" [t| Bool |]