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