Commit 1410fa8d authored by Michael Hanselmann's avatar Michael Hanselmann
Browse files

Add opcode to prepare export

To prepare a remote export, the X509 key and certificate need to be generated.
A handshake value is also returned for an easier check whether both clusters
share the same cluster domain secret.
Signed-off-by: default avatarMichael Hanselmann <>
Reviewed-by: default avatarIustin Pop <>
parent b38599d8
......@@ -8822,6 +8822,66 @@ class LUQueryExports(NoHooksLU):
return result
class LUPrepareExport(NoHooksLU):
"""Prepares an instance for an export and returns useful information.
_OP_REQP = ["instance_name", "mode"]
REQ_BGL = False
def CheckArguments(self):
"""Check the arguments.
if self.op.mode not in constants.EXPORT_MODES:
raise errors.OpPrereqError("Invalid export mode %r" % self.op.mode,
def ExpandNames(self):
def CheckPrereq(self):
"""Check prerequisites.
instance_name = self.op.instance_name
self.instance = self.cfg.GetInstanceInfo(instance_name)
assert self.instance is not None, \
"Cannot retrieve locked instance %s" % self.op.instance_name
_CheckNodeOnline(self, self.instance.primary_node)
self._cds = _GetClusterDomainSecret()
def Exec(self, feedback_fn):
"""Prepares an instance for an export.
instance = self.instance
if self.op.mode == constants.EXPORT_MODE_REMOTE:
salt = utils.GenerateSecret(8)
feedback_fn("Generating X509 certificate on %s" % instance.primary_node)
result = self.rpc.call_x509_cert_create(instance.primary_node,
result.Raise("Can't create X509 key and certificate on %s" % result.node)
(name, cert_pem) = result.payload
cert = OpenSSL.crypto.load_certificate(OpenSSL.crypto.FILETYPE_PEM,
return {
"handshake": masterd.instance.ComputeRemoteExportHandshake(self._cds),
"x509_key_name": (name, utils.Sha1Hmac(self._cds, name, salt=salt),
"x509_ca": utils.SignX509Certificate(cert, self._cds, salt),
return None
class LUExportInstance(LogicalUnit):
"""Export an instance to an image in the cluster.
......@@ -336,6 +336,13 @@ LOCKS_APPEND = 'append'
# Remote import/export handshake message and version
RIE_HANDSHAKE = "Hi, I'm Ganeti"
# Remote import/export certificate validity in seconds
RIE_CERT_VALIDITY = 24 * 60 * 60
......@@ -1105,3 +1105,50 @@ class ExportInstanceHelper:
assert len(self._removed_snaps) == len(self._instance.disks)
for idx in range(len(self._instance.disks)):
def _GetImportExportHandshakeMessage(version):
"""Returns the handshake message for a RIE protocol version.
@type version: number
return "%s:%s" % (version, constants.RIE_HANDSHAKE)
def ComputeRemoteExportHandshake(cds):
"""Computes the remote import/export handshake.
@type cds: string
@param cds: Cluster domain secret
salt = utils.GenerateSecret(8)
msg = _GetImportExportHandshakeMessage(constants.RIE_VERSION)
return (constants.RIE_VERSION, utils.Sha1Hmac(cds, msg, salt=salt), salt)
def CheckRemoteExportHandshake(cds, handshake):
"""Checks the handshake of a remote import/export.
@type cds: string
@param cds: Cluster domain secret
@type handshake: sequence
@param handshake: Handshake sent by remote peer
(version, hmac_digest, hmac_salt) = handshake
except (TypeError, ValueError), err:
return "Invalid data: %s" % err
if not utils.VerifySha1Hmac(cds, _GetImportExportHandshakeMessage(version),
hmac_digest, salt=hmac_salt):
return "Hash didn't match, clusters don't share the same domain secret"
if version != constants.RIE_VERSION:
return ("Clusters don't have the same remote import/export protocol"
" (local=%s, remote=%s)" %
(constants.RIE_VERSION, version))
return None
......@@ -210,6 +210,7 @@ class Processor(object):
opcodes.OpDiagnoseOS: cmdlib.LUDiagnoseOS,
# exports lu
opcodes.OpQueryExports: cmdlib.LUQueryExports,
opcodes.OpPrepareExport: cmdlib.LUPrepareExport,
opcodes.OpExportInstance: cmdlib.LUExportInstance,
opcodes.OpRemoveExport: cmdlib.LURemoveExport,
# tags lu
......@@ -653,6 +653,20 @@ class OpQueryExports(OpCode):
__slots__ = ["nodes", "use_locking"]
class OpPrepareExport(OpCode):
"""Prepares an instance export.
@ivar instance_name: Instance name
@ivar mode: Export mode (one of L{constants.EXPORT_MODES})
OP_DSC_FIELD = "instance_name"
__slots__ = [
"instance_name", "mode",
class OpExportInstance(OpCode):
"""Export an instance."""
......@@ -25,11 +25,13 @@ import os
import sys
import unittest
from ganeti import constants
from ganeti import utils
from ganeti import masterd
from ganeti.masterd.instance import \
ImportExportTimeouts, _TimeoutExpired, _DiskImportExportBase
ImportExportTimeouts, _TimeoutExpired, _DiskImportExportBase, \
ComputeRemoteExportHandshake, CheckRemoteExportHandshake
import testutils
......@@ -56,5 +58,33 @@ class TestMisc(unittest.TestCase):
None, None, None, None, None, None, None)
class TestRieHandshake(unittest.TestCase):
def test(self):
cds = "cd-secret"
hs = ComputeRemoteExportHandshake(cds)
self.assertEqual(len(hs), 3)
self.assertEqual(hs[0], constants.RIE_VERSION)
self.assertEqual(CheckRemoteExportHandshake(cds, hs), None)
def testCheckErrors(self):
self.assert_(CheckRemoteExportHandshake(None, None))
self.assert_(CheckRemoteExportHandshake("", ""))
self.assert_(CheckRemoteExportHandshake("", ("xyz", "foo")))
def testCheckWrongHash(self):
cds = "cd-secret999"
self.assert_(CheckRemoteExportHandshake(cds, (0, "fakehash", "xyz")))
def testCheckWrongVersion(self):
version = 14887
self.assertNotEqual(version, constants.RIE_VERSION)
cds = "c28ac99"
salt = "a19cf8cc06"
msg = "%s:%s" % (version, constants.RIE_HANDSHAKE)
hs = (version, utils.Sha1Hmac(cds, msg, salt=salt), salt)
self.assert_(CheckRemoteExportHandshake(cds, hs))
if __name__ == "__main__":
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment