diff --git a/htest/Test/Ganeti/OpCodes.hs b/htest/Test/Ganeti/OpCodes.hs
index 252768b0c84834d08785f9584b58d5cef44db383..c690b07d59931d6879ab35696550e00b38b85164 100644
--- a/htest/Test/Ganeti/OpCodes.hs
+++ b/htest/Test/Ganeti/OpCodes.hs
@@ -94,6 +94,11 @@ instance (Arbitrary a) => Arbitrary (SetParamsMods a) where
                     , SetParamsNew        <$> arbitrary
                     ]
 
+instance Arbitrary ExportTarget where
+  arbitrary = oneof [ ExportTargetLocal <$> genNodeNameNE
+                    , ExportTargetRemote <$> pure []
+                    ]
+
 instance Arbitrary OpCodes.OpCode where
   arbitrary = do
     op_id <- elements OpCodes.allOpIDs
@@ -253,6 +258,38 @@ instance Arbitrary OpCodes.OpCode where
       "OP_INSTANCE_CHANGE_GROUP" ->
         OpCodes.OpInstanceChangeGroup <$> getFQDN <*> arbitrary <*>
           getMaybe genNameNE <*> getMaybe (resize maxNodes (listOf genNameNE))
+      "OP_GROUP_ADD" ->
+        OpCodes.OpGroupAdd <$> genNameNE <*> arbitrary <*>
+          emptyMUD <*> getMaybe genEmptyContainer <*>
+          emptyMUD <*> emptyMUD <*> emptyMUD
+      "OP_GROUP_ASSIGN_NODES" ->
+        OpCodes.OpGroupAssignNodes <$> genNameNE <*> arbitrary <*>
+          genNodeNamesNE
+      "OP_GROUP_QUERY" ->
+        OpCodes.OpGroupQuery <$> genFieldsNE <*> genNamesNE
+      "OP_GROUP_SET_PARAMS" ->
+        OpCodes.OpGroupSetParams <$> genNameNE <*> arbitrary <*>
+          emptyMUD <*> getMaybe genEmptyContainer <*>
+          emptyMUD <*> emptyMUD <*> emptyMUD
+      "OP_GROUP_REMOVE" ->
+        OpCodes.OpGroupRemove <$> genNameNE
+      "OP_GROUP_RENAME" ->
+        OpCodes.OpGroupRename <$> genNameNE <*> genNameNE
+      "OP_GROUP_EVACUATE" ->
+        OpCodes.OpGroupEvacuate <$> genNameNE <*> arbitrary <*>
+          getMaybe genNameNE <*> getMaybe genNamesNE
+      "OP_OS_DIAGNOSE" ->
+        OpCodes.OpOsDiagnose <$> genFieldsNE <*> genNamesNE
+      "OP_BACKUP_QUERY" ->
+        OpCodes.OpBackupQuery <$> arbitrary <*> genNodeNamesNE
+      "OP_BACKUP_PREPARE" ->
+        OpCodes.OpBackupPrepare <$> getFQDN <*> arbitrary
+      "OP_BACKUP_EXPORT" ->
+        OpCodes.OpBackupExport <$> getFQDN <*> arbitrary <*>
+          arbitrary <*> arbitrary <*> arbitrary <*> arbitrary <*>
+          getMaybe (pure []) <*> getMaybe genNameNE
+      "OP_BACKUP_REMOVE" ->
+        OpCodes.OpBackupRemove <$> getFQDN
       _ -> fail $ "Undefined arbitrary for opcode " ++ op_id
 
 -- * Helper functions
@@ -291,6 +328,10 @@ genNodeNameNE = getFQDN >>= mkNonEmpty
 genNameNE :: Gen NonEmptyString
 genNameNE = getName >>= mkNonEmpty
 
+-- | Gets a list of names (non-fqdn) in non-empty type.
+genNamesNE :: Gen [NonEmptyString]
+genNamesNE = resize maxNodes (listOf genNameNE)
+
 -- | Returns a list of non-empty fields.
 genFieldsNE :: Gen [NonEmptyString]
 genFieldsNE = getFields >>= mapM mkNonEmpty
diff --git a/htest/Test/Ganeti/Types.hs b/htest/Test/Ganeti/Types.hs
index 3e487a5d2cc961464f4d5c84a557376115572df1..d7e959f71887119ac6756e447bc71d452ea6e42b 100644
--- a/htest/Test/Ganeti/Types.hs
+++ b/htest/Test/Ganeti/Types.hs
@@ -97,6 +97,8 @@ $(genArbitrary ''InstCreateMode)
 
 $(genArbitrary ''RebootType)
 
+$(genArbitrary ''ExportMode)
+
 -- * Properties
 
 prop_AllocPolicy_serialisation :: AllocPolicy -> Property
@@ -206,6 +208,10 @@ prop_InstCreateMode_serialisation = testSerialisation
 prop_RebootType_serialisation :: RebootType -> Property
 prop_RebootType_serialisation = testSerialisation
 
+-- | Test 'ExportMode' serialisation.
+prop_ExportMode_serialisation :: ExportMode -> Property
+prop_ExportMode_serialisation = testSerialisation
+
 testSuite "Types"
   [ 'prop_AllocPolicy_serialisation
   , 'prop_DiskTemplate_serialisation
@@ -229,4 +235,5 @@ testSuite "Types"
   , 'prop_FileDriver_serialisation
   , 'prop_InstCreateMode_serialisation
   , 'prop_RebootType_serialisation
+  , 'prop_ExportMode_serialisation
   ]
diff --git a/htools/Ganeti/OpCodes.hs b/htools/Ganeti/OpCodes.hs
index ad7010f8a169ab09ab8ed7d6c5c3cca6db3caa9d..8d08632cc3519dd582e9c1f8f8730c5264b20ea7 100644
--- a/htools/Ganeti/OpCodes.hs
+++ b/htools/Ganeti/OpCodes.hs
@@ -376,6 +376,68 @@ $(genOpCode "OpCode"
      , pIallocator
      , pTargetGroups
      ])
+  , ("OpGroupAdd",
+     [ pGroupName
+     , pNodeGroupAllocPolicy
+     , pGroupNodeParams
+     , pDiskParams
+     , pHvState
+     , pDiskState
+     , pIpolicy
+     ])
+  , ("OpGroupAssignNodes",
+     [ pGroupName
+     , pForce
+     , pRequiredNodes
+     ])
+  , ("OpGroupQuery",
+     [ pOutputFields
+     , pNames
+     ])
+  , ("OpGroupSetParams",
+     [ pGroupName
+     , pNodeGroupAllocPolicy
+     , pGroupNodeParams
+     , pDiskParams
+     , pHvState
+     , pDiskState
+     , pIpolicy
+     ])
+  , ("OpGroupRemove",
+     [ pGroupName ])
+  , ("OpGroupRename",
+     [ pGroupName
+     , pNewName
+     ])
+  , ("OpGroupEvacuate",
+     [ pGroupName
+     , pEarlyRelease
+     , pIallocator
+     , pTargetGroups
+     ])
+  , ("OpOsDiagnose",
+     [ pOutputFields
+     , pNames ])
+  , ("OpBackupQuery",
+     [ pUseLocking
+     , pNodes
+     ])
+  , ("OpBackupPrepare",
+     [ pInstanceName
+     , pExportMode
+     ])
+  , ("OpBackupExport",
+     [ pInstanceName
+     , pShutdownTimeout
+     , pExportTargetNode
+     , pRemoveInstance
+     , pIgnoreRemoveFailures
+     , pExportMode
+     , pX509KeyName
+     , pX509DestCA
+     ])
+  , ("OpBackupRemove",
+     [ pInstanceName ])
   ])
 
 -- | Returns the OP_ID for a given opcode value.
diff --git a/htools/Ganeti/OpParams.hs b/htools/Ganeti/OpParams.hs
index ed80ed9a584e2870d4e7bff64321507e618e00b6..955599c26539df43c7d4b101079a81e77dac6dcd 100644
--- a/htools/Ganeti/OpParams.hs
+++ b/htools/Ganeti/OpParams.hs
@@ -47,6 +47,7 @@ module Ganeti.OpParams
   , RecreateDisksInfo(..)
   , DdmOldChanges(..)
   , SetParamsMods(..)
+  , ExportTarget(..)
   , pInstanceName
   , pInstances
   , pName
@@ -137,6 +138,7 @@ module Ganeti.OpParams
   , pVmCapable
   , pNames
   , pNodes
+  , pRequiredNodes
   , pStorageType
   , pStorageChanges
   , pMasterCandidate
@@ -179,6 +181,12 @@ module Ganeti.OpParams
   , pDiskChgAmount
   , pDiskChgAbsolute
   , pTargetGroups
+  , pExportMode
+  , pExportTargetNode
+  , pRemoveInstance
+  , pIgnoreRemoveFailures
+  , pX509KeyName
+  , pX509DestCA
   ) where
 
 import Control.Monad (liftM)
@@ -421,6 +429,27 @@ instance (JSON a) => JSON (SetParamsMods a) where
   showJSON (SetParamsNew v) = showJSON v
   readJSON = readSetParams
 
+-- | Custom type for target_node parameter of OpBackupExport, which
+-- varies depending on mode. FIXME: this uses an UncheckedList 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 UncheckedList
+                    deriving (Eq, Read, Show)
+
+-- | 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
+
 -- * Parameters
 
 -- | A required instance name (for single-instance LUs).
@@ -769,7 +798,7 @@ pInstNics = simpleField "nics" [t| [INicParams] |]
 pNdParams :: Field
 pNdParams = optionalField $ simpleField "ndparams" [t| UncheckedDict |]
 
--- | Cluster-wipe ipolict specs.
+-- | Cluster-wide ipolicy specs.
 pIpolicy :: Field
 pIpolicy = optionalField $ simpleField "ipolicy" [t| UncheckedDict |]
 
@@ -872,6 +901,11 @@ pNames = defaultField [| [] |] $ simpleField "names" [t| [NonEmptyString] |]
 pNodes :: Field
 pNodes = defaultField [| [] |] $ simpleField "nodes" [t| [NonEmptyString] |]
 
+-- | Required list of node names.
+pRequiredNodes :: Field
+pRequiredNodes =
+  renameField "ReqNodes " $ simpleField "nodes" [t| [NonEmptyString] |]
+
 -- | Storage type.
 pStorageType :: Field
 pStorageType = simpleField "storage_type" [t| StorageType |]
@@ -1051,3 +1085,30 @@ pDiskChgAbsolute = renameField "DiskChkAbsolute" $ defaultFalse "absolute"
 pTargetGroups :: Field
 pTargetGroups =
   optionalField $ simpleField "target_groups" [t| [NonEmptyString] |]
+
+-- | Export mode field.
+pExportMode :: Field
+pExportMode =
+  renameField "ExportMode" $ simpleField "mode" [t| ExportMode |]
+
+-- | Export target_node field, depends on mode.
+pExportTargetNode :: Field
+pExportTargetNode =
+  renameField "ExportTarget" $
+  simpleField "target_node" [t| ExportTarget |]
+
+-- | Whether to remove instance after export.
+pRemoveInstance :: Field
+pRemoveInstance = defaultFalse "remove_instance"
+
+-- | Whether to ignore failures while removing instances.
+pIgnoreRemoveFailures :: Field
+pIgnoreRemoveFailures = defaultFalse "ignore_remove_failures"
+
+-- | Name of X509 key (remote export only).
+pX509KeyName :: Field
+pX509KeyName = optionalField $ simpleField "x509_key_name" [t| UncheckedList |]
+
+-- | Destination X509 CA (remote export only).
+pX509DestCA :: Field
+pX509DestCA = optionalNEStringField "destination_x509_ca"
diff --git a/htools/Ganeti/Types.hs b/htools/Ganeti/Types.hs
index 1b4e9f5a309ec40660a4f89e7dcf627d820287a9..170d13f8e48a591df9c89c6ce7b5bd342de1ef88 100644
--- a/htools/Ganeti/Types.hs
+++ b/htools/Ganeti/Types.hs
@@ -63,6 +63,7 @@ module Ganeti.Types
   , FileDriver(..)
   , InstCreateMode(..)
   , RebootType(..)
+  , ExportMode(..)
   ) where
 
 import qualified Text.JSON as JSON
@@ -286,3 +287,10 @@ $(THH.declareSADT "RebootType"
   , ("RebootFull", 'C.instanceRebootFull)
   ])
 $(THH.makeJSONInstance ''RebootType)
+
+-- | Export modes.
+$(THH.declareSADT "ExportMode"
+  [ ("ExportModeLocal",  'C.exportModeLocal)
+  , ("ExportModeRemove", 'C.exportModeRemote)
+  ])
+$(THH.makeJSONInstance ''ExportMode)
diff --git a/lib/opcodes.py b/lib/opcodes.py
index 2ce5487a10e219b97a13b6bb3600c070455af021..2d34c567fbfa15614ae4ff5b4e3c2ef81ad3eaf6 100644
--- a/lib/opcodes.py
+++ b/lib/opcodes.py
@@ -1816,11 +1816,12 @@ class OpBackupPrepare(OpCode):
 class OpBackupExport(OpCode):
   """Export an instance.
 
-  For local exports, the export destination is the node name. For remote
-  exports, the export destination is a list of tuples, each consisting of
-  hostname/IP address, port, HMAC and HMAC salt. The HMAC is calculated using
-  the cluster domain secret over the value "${index}:${hostname}:${port}". The
-  destination X509 CA must be a signed certificate.
+  For local exports, the export destination is the node name. For
+  remote exports, the export destination is a list of tuples, each
+  consisting of hostname/IP address, port, magic, HMAC and HMAC
+  salt. The HMAC is calculated using the cluster domain secret over
+  the value "${index}:${hostname}:${port}". The destination X509 CA
+  must be a signed certificate.
 
   @ivar mode: Export mode (one of L{constants.EXPORT_MODES})
   @ivar target_node: Export destination