diff mbox

[v4,10/10] Add the drive-reopen command

Message ID 1331056563-7503-11-git-send-email-pbonzini@redhat.com
State New
Headers show

Commit Message

Paolo Bonzini March 6, 2012, 5:56 p.m. UTC
From: Federico Simoncelli <fsimonce@redhat.com>

Signed-off-by: Federico Simoncelli <fsimonce@redhat.com>
Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
---
 blockdev.c       |   63 ++++++++++++++++++++++++++++++++++++++++++++++++++++++
 hmp-commands.hx  |   16 +++++++++++++
 hmp.c            |   11 +++++++++
 hmp.h            |    1 +
 qapi-schema.json |   22 ++++++++++++++++++
 qmp-commands.hx  |   30 +++++++++++++++++++++++++
 6 files changed, 143 insertions(+), 0 deletions(-)

Comments

Eric Blake March 13, 2012, 8:48 p.m. UTC | #1
On 03/06/2012 10:56 AM, Paolo Bonzini wrote:
> From: Federico Simoncelli <fsimonce@redhat.com>
> 
> Signed-off-by: Federico Simoncelli <fsimonce@redhat.com>
> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>

>  ##
> +# @drive-reopen
> +#
> +# Assigns a new image file to a device.
> +#
> +# @device: the name of the device for which we are changing the image file.
> +#
> +# @new-image-file: the target of the new image. If the file doesn't exists the
> +#                  command will fail.
> +#
> +# @format: #optional the format of the new image, default is 'qcow2'.
> +#
> +# Returns: nothing on success
> +#          If @device is not a valid block device, DeviceNotFound
> +#          If @new-image-file can't be opened, OpenFileFailed
> +#          If @format is invalid, InvalidBlockFormat
> +#
> +# Since 1.1
> +##
> +{ 'command': 'drive-reopen',
> +  'data': { 'device': 'str', 'new-image-file': 'str', '*format': 'str' } }

I still think we need a 'drive-reopen' action included in 'transaction',
as an 11/10 on this series.  For disk migration, it is true that you can
migrate one disk at a time, and therefore only need to reopen one disk
at a time, to get the guarantee that for a single disk image, the
current state of that image will be guaranteed to be consistent using
only one storage domain.

But since the API allows the creation of two mirrors in one command, I'm
worried that someone will try to start a mirror on two disks at once,
but then be stuck doing two separate 'drive-reopen' commands.  If the
first succeeds but the second fails, you have now stranded the qemu
process across two storage domains, which is exactly what we were trying
to avoid in the first place by inventing transactions.  That is, even if
all disks are individually consistent in a single domain, the act of
migrating then reopening one disk at a time means you will have a window
where disk 1 and disk 2 are opened on different storage domains.

Besides, I'm planning on implementing libvirt support for the
'drive-reopen' command by adding a flag to virDomainSnapshotDelete
(basically, the presence of the flag states that for all mirrored disks
in a given snapshot, libvirt will then issue a drive-reopen that pivots
qemu over to the mirror, and finally delete the snapshot now that
mirroring is no longer needed).  With separate commands, if drive-reopen
on disk 1 succeeds, then drive-reopen on disk 2 fails, I can attempt a
rollback by doing another drive-reopen on disk 1; but the rollback will
be incomplete since I have lost the ability to reopen the mirroring.  I
would much rather issue a 'transaction' with multiple reopen commands,
and knowing that either all disks were reopened and the mirrors
discarded, or that none were reopened and the mirroring remains intact.
Federico Simoncelli March 14, 2012, 12:14 a.m. UTC | #2
----- Original Message -----
> From: "Eric Blake" <eblake@redhat.com>
> To: "Paolo Bonzini" <pbonzini@redhat.com>
> Cc: kwolf@redhat.com, fsimonce@redhat.com, qemu-devel@nongnu.org, stefanha@linux.vnet.ibm.com, lcapitulino@redhat.com
> Sent: Tuesday, March 13, 2012 9:48:10 PM
> Subject: Re: [Qemu-devel] [PATCH v4 10/10] Add the drive-reopen command
> 
> On 03/06/2012 10:56 AM, Paolo Bonzini wrote:
> > From: Federico Simoncelli <fsimonce@redhat.com>
> > 
> > Signed-off-by: Federico Simoncelli <fsimonce@redhat.com>
> > Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
> 
> >  ##
> > +# @drive-reopen
> > +#
> > +# Assigns a new image file to a device.
> > +#
> > +# @device: the name of the device for which we are changing the
> > image file.
> > +#
> > +# @new-image-file: the target of the new image. If the file
> > doesn't exists the
> > +#                  command will fail.
> > +#
> > +# @format: #optional the format of the new image, default is
> > 'qcow2'.
> > +#
> > +# Returns: nothing on success
> > +#          If @device is not a valid block device, DeviceNotFound
> > +#          If @new-image-file can't be opened, OpenFileFailed
> > +#          If @format is invalid, InvalidBlockFormat
> > +#
> > +# Since 1.1
> > +##
> > +{ 'command': 'drive-reopen',
> > +  'data': { 'device': 'str', 'new-image-file': 'str', '*format':
> > 'str' } }
> 
> I still think we need a 'drive-reopen' action included in
> 'transaction',
> as an 11/10 on this series.  For disk migration, it is true that you
> can
> migrate one disk at a time, and therefore only need to reopen one
> disk
> at a time, to get the guarantee that for a single disk image, the
> current state of that image will be guaranteed to be consistent using
> only one storage domain.

I'm not sure if this was already addressed on this mailing list but
the main problem is that as general rule a qcow file cannot be opened
in r/w mode twice. I believe there was only one exception to that
with the live migration and it generated several issues.

That said, reopen is really hard to be implemented as a transaction
without breaking that rule. For example in the blkmirror case you'd
need to open the destination image in r/w while the mirroring is in
action (already having the same image in r/w mode).

There are several solutions here but they are either really hard to
implement or non definitive. For example:

* We could try to implement the reopen command for each special case,
  eg: blkmirror, reopening the same image, etc... and in such cases
  reusing the same bs that we already have. The downside is that this
  command will be coupled with all this special cases.

* We could use the transaction APIs without actually making it
  transaction (if we fail in the middle we can't rollback). The only
  advantage of this is that we'd provide a consistent API to libvirt
  and we would postpone the problem to the future. Anyway I strongly
  discourage this as it's completely unsafe and it's going to break
  the transaction semantic. Moreover it's a solution that relies too
  much on the hope of finding something appropriate in the future.

* We could leave it as it is, a distinct command that is not part of
  the transaction and that it's closing the old image before opening
  the new one.

> But since the API allows the creation of two mirrors in one command,
> I'm
> worried that someone will try to start a mirror on two disks at once,
> but then be stuck doing two separate 'drive-reopen' commands.  If the
> first succeeds but the second fails, you have now stranded the qemu
> process across two storage domains, which is exactly what we were
> trying
> to avoid in the first place by inventing transactions.  That is, even
> if
> all disks are individually consistent in a single domain, the act of
> migrating then reopening one disk at a time means you will have a
> window
> where disk 1 and disk 2 are opened on different storage domains.

This is not completely correct, the main intent was to not spread one
image chain across two storage domains (making it incomplete if one of
them was missing). In the next oVirt release a VM can have different
disks on different storage domains, so this wouldn't be a special case
but just a normal situation.
Kevin Wolf March 14, 2012, 9:17 a.m. UTC | #3
Am 13.03.2012 21:48, schrieb Eric Blake:
> On 03/06/2012 10:56 AM, Paolo Bonzini wrote:
>> From: Federico Simoncelli <fsimonce@redhat.com>
>>
>> Signed-off-by: Federico Simoncelli <fsimonce@redhat.com>
>> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
> 
>>  ##
>> +# @drive-reopen
>> +#
>> +# Assigns a new image file to a device.
>> +#
>> +# @device: the name of the device for which we are changing the image file.
>> +#
>> +# @new-image-file: the target of the new image. If the file doesn't exists the
>> +#                  command will fail.
>> +#
>> +# @format: #optional the format of the new image, default is 'qcow2'.
>> +#
>> +# Returns: nothing on success
>> +#          If @device is not a valid block device, DeviceNotFound
>> +#          If @new-image-file can't be opened, OpenFileFailed
>> +#          If @format is invalid, InvalidBlockFormat
>> +#
>> +# Since 1.1
>> +##
>> +{ 'command': 'drive-reopen',
>> +  'data': { 'device': 'str', 'new-image-file': 'str', '*format': 'str' } }
> 
> I still think we need a 'drive-reopen' action included in 'transaction',
> as an 11/10 on this series.

If we want to do this,  it needs to be the same patch, as we couple the
transaction actions with top-level commands as long as there is no other
way to discover the possible actions. And it probably makes more sense
anyway, because the top-level command would be just a thin wrapper
around the transactional one.

Only problem is that just moving the code there doesn't make it suitable
for a transaction and doing an all-or-nothing drive-reopen isn't quite
trivial.

Kevin
Paolo Bonzini March 14, 2012, 9:19 a.m. UTC | #4
Il 14/03/2012 10:17, Kevin Wolf ha scritto:
> If we want to do this,  it needs to be the same patch, as we couple the
> transaction actions with top-level commands as long as there is no other
> way to discover the possible actions. And it probably makes more sense
> anyway, because the top-level command would be just a thin wrapper
> around the transactional one.
> 
> Only problem is that just moving the code there doesn't make it suitable
> for a transaction and doing an all-or-nothing drive-reopen isn't quite
> trivial.

We can also add a "transactionable" item to query-commands.  If we do it
after 1.1, in absence of it only blockdev-snapshot-sync and drive-mirror
are transactionable.  Otherwise we can do it now.

Paolo
Kevin Wolf March 14, 2012, 9:34 a.m. UTC | #5
Am 14.03.2012 01:14, schrieb Federico Simoncelli:
> ----- Original Message -----
>> From: "Eric Blake" <eblake@redhat.com>
>> To: "Paolo Bonzini" <pbonzini@redhat.com>
>> Cc: kwolf@redhat.com, fsimonce@redhat.com, qemu-devel@nongnu.org, stefanha@linux.vnet.ibm.com, lcapitulino@redhat.com
>> Sent: Tuesday, March 13, 2012 9:48:10 PM
>> Subject: Re: [Qemu-devel] [PATCH v4 10/10] Add the drive-reopen command
>>
>> On 03/06/2012 10:56 AM, Paolo Bonzini wrote:
>>> From: Federico Simoncelli <fsimonce@redhat.com>
>>>
>>> Signed-off-by: Federico Simoncelli <fsimonce@redhat.com>
>>> Signed-off-by: Paolo Bonzini <pbonzini@redhat.com>
>>
>>>  ##
>>> +# @drive-reopen
>>> +#
>>> +# Assigns a new image file to a device.
>>> +#
>>> +# @device: the name of the device for which we are changing the
>>> image file.
>>> +#
>>> +# @new-image-file: the target of the new image. If the file
>>> doesn't exists the
>>> +#                  command will fail.
>>> +#
>>> +# @format: #optional the format of the new image, default is
>>> 'qcow2'.
>>> +#
>>> +# Returns: nothing on success
>>> +#          If @device is not a valid block device, DeviceNotFound
>>> +#          If @new-image-file can't be opened, OpenFileFailed
>>> +#          If @format is invalid, InvalidBlockFormat
>>> +#
>>> +# Since 1.1
>>> +##
>>> +{ 'command': 'drive-reopen',
>>> +  'data': { 'device': 'str', 'new-image-file': 'str', '*format':
>>> 'str' } }
>>
>> I still think we need a 'drive-reopen' action included in
>> 'transaction',
>> as an 11/10 on this series.  For disk migration, it is true that you
>> can
>> migrate one disk at a time, and therefore only need to reopen one
>> disk
>> at a time, to get the guarantee that for a single disk image, the
>> current state of that image will be guaranteed to be consistent using
>> only one storage domain.
> 
> I'm not sure if this was already addressed on this mailing list but
> the main problem is that as general rule a qcow file cannot be opened
> in r/w mode twice. I believe there was only one exception to that
> with the live migration and it generated several issues.

In fact the same is true for any image. There are just some special
cases that happen to work anyway, but using them is not more than a hack.

> That said, reopen is really hard to be implemented as a transaction
> without breaking that rule. For example in the blkmirror case you'd
> need to open the destination image in r/w while the mirroring is in
> action (already having the same image in r/w mode).
> 
> There are several solutions here but they are either really hard to
> implement or non definitive. For example:
> 
> * We could try to implement the reopen command for each special case,
>   eg: blkmirror, reopening the same image, etc... and in such cases
>   reusing the same bs that we already have. The downside is that this
>   command will be coupled with all this special cases.

The problem we're trying to solve is that we have a graph of open
BlockDriverStates (connected by bs->file, backing file relations etc.)
and we want to transform it into a different graph of open BDSs
atomically, reusing zero, one or many of the existing BDSs, and possibly
with changed properties (cache mode, read-only, etc.)

What we can have reasonably easily (there are patches floating around,
they just need to be completed), is a bdrv_reopen() that changes flags
on one given BDS, without changing the file it's backed by. This is
already broken up into prepare/commit/abort stages as we need it to
reopen VMDK's split images safely.

In theory this should be enough to build the new graph by opening yet
unused BDSs, preparing the reopen of reused ones and only if all of that
was successful, committing the bdrv_reopen and changing the relations
between the nodes. I hope that at the same time it's clear that this
isn't exactly trivial to implement.

> * We could use the transaction APIs without actually making it
>   transaction (if we fail in the middle we can't rollback). The only
>   advantage of this is that we'd provide a consistent API to libvirt
>   and we would postpone the problem to the future. Anyway I strongly
>   discourage this as it's completely unsafe and it's going to break
>   the transaction semantic. Moreover it's a solution that relies too
>   much on the hope of finding something appropriate in the future.

This is not an option. Advertising transactional behaviour and not
implementing it is just plain wrong.

> * We could leave it as it is, a distinct command that is not part of
>   the transaction and that it's closing the old image before opening
>   the new one.

Yes, this would be the short-term preliminary solution. I would tend to
leave it to downstreams to implement it as an extension, though.

> This is not completely correct, the main intent was to not spread one
> image chain across two storage domains (making it incomplete if one of
> them was missing). In the next oVirt release a VM can have different
> disks on different storage domains, so this wouldn't be a special case
> but just a normal situation.

The problem with this kind of argument is that we're not developing only
for oVirt, but need to look for what makes sense for any management tool
(or even just direct users of qemu).

Kevin
Kevin Wolf March 14, 2012, 9:35 a.m. UTC | #6
Am 14.03.2012 10:19, schrieb Paolo Bonzini:
> Il 14/03/2012 10:17, Kevin Wolf ha scritto:
>> If we want to do this,  it needs to be the same patch, as we couple the
>> transaction actions with top-level commands as long as there is no other
>> way to discover the possible actions. And it probably makes more sense
>> anyway, because the top-level command would be just a thin wrapper
>> around the transactional one.
>>
>> Only problem is that just moving the code there doesn't make it suitable
>> for a transaction and doing an all-or-nothing drive-reopen isn't quite
>> trivial.
> 
> We can also add a "transactionable" item to query-commands.  If we do it
> after 1.1, in absence of it only blockdev-snapshot-sync and drive-mirror
> are transactionable.  Otherwise we can do it now.

Sounds reasonable. If we want to do it, I would prefer to do it right now.

Kevin
Eric Blake March 14, 2012, 1:11 p.m. UTC | #7
On 03/14/2012 03:34 AM, Kevin Wolf wrote:

>> That said, reopen is really hard to be implemented as a transaction
>> without breaking that rule. For example in the blkmirror case you'd
>> need to open the destination image in r/w while the mirroring is in
>> action (already having the same image in r/w mode).
>>
>> There are several solutions here but they are either really hard to
>> implement or non definitive. For example:
>>
>> * We could try to implement the reopen command for each special case,
>>   eg: blkmirror, reopening the same image, etc... and in such cases
>>   reusing the same bs that we already have. The downside is that this
>>   command will be coupled with all this special cases.
> 
> The problem we're trying to solve is that we have a graph of open
> BlockDriverStates (connected by bs->file, backing file relations etc.)
> and we want to transform it into a different graph of open BDSs
> atomically, reusing zero, one or many of the existing BDSs, and possibly
> with changed properties (cache mode, read-only, etc.)
> 
> What we can have reasonably easily (there are patches floating around,
> they just need to be completed), is a bdrv_reopen() that changes flags
> on one given BDS, without changing the file it's backed by. This is
> already broken up into prepare/commit/abort stages as we need it to
> reopen VMDK's split images safely.
> 
> In theory this should be enough to build the new graph by opening yet
> unused BDSs, preparing the reopen of reused ones and only if all of that
> was successful, committing the bdrv_reopen and changing the relations
> between the nodes. I hope that at the same time it's clear that this
> isn't exactly trivial to implement.

Agreed that 1) it looks implementable, and 2) it does not look trivial.

> 
>> * We could use the transaction APIs without actually making it
>>   transaction (if we fail in the middle we can't rollback). The only
>>   advantage of this is that we'd provide a consistent API to libvirt
>>   and we would postpone the problem to the future. Anyway I strongly
>>   discourage this as it's completely unsafe and it's going to break
>>   the transaction semantic. Moreover it's a solution that relies too
>>   much on the hope of finding something appropriate in the future.
> 
> This is not an option. Advertising transactional behaviour and not
> implementing it is just plain wrong.

Absolutely concur.  From libvirt's perspective, I will be assuming that:

if 'transaction' exists, then it supports 'blockdev-snapshot-sync' and
'drive-mirror' (assuming that these are the only two commands that are
in 'transaction' in qemu 1.1), but nothing else is safe to use without a
further probe.  Then, if down the road, we go through the pain of making
'drive-reopen' transactionable, then there will be some new
introspection command (one idea was an optional argument to
query-commands which limits output to just the commands that can also
occur in a 'transaction'), and libvirt will have to use that
introspection before using the additional features.

> 
>> * We could leave it as it is, a distinct command that is not part of
>>   the transaction and that it's closing the old image before opening
>>   the new one.
> 
> Yes, this would be the short-term preliminary solution. I would tend to
> leave it to downstreams to implement it as an extension, though.

Correct - I'm fine with libvirt using the direct, non-atomic,
'drive-reopen' for the immediate needs of oVirt while we work on a more
complete solution for down the road.

> 
>> This is not completely correct, the main intent was to not spread one
>> image chain across two storage domains (making it incomplete if one of
>> them was missing). In the next oVirt release a VM can have different
>> disks on different storage domains, so this wouldn't be a special case
>> but just a normal situation.
> 
> The problem with this kind of argument is that we're not developing only
> for oVirt, but need to look for what makes sense for any management tool
> (or even just direct users of qemu).

Indeed - that's why I still think it's dangerous to not have an atomic
'drive-reopen', even if oVirt can be coded to work in spite of an
initial implementation being non-atomic.  But there's a difference
between wish-lists (atomic reopen) and practicality (what can we
implement now), and I'm not going to insist on the impossible :)
Federico Simoncelli March 14, 2012, 1:29 p.m. UTC | #8
----- Original Message -----
> From: "Kevin Wolf" <kwolf@redhat.com>
> To: "Federico Simoncelli" <fsimonce@redhat.com>
> Cc: "Eric Blake" <eblake@redhat.com>, qemu-devel@nongnu.org, stefanha@linux.vnet.ibm.com, lcapitulino@redhat.com,
> "Paolo Bonzini" <pbonzini@redhat.com>, "Markus Armbruster" <armbru@redhat.com>
> Sent: Wednesday, March 14, 2012 10:34:08 AM
> Subject: Re: [Qemu-devel] [PATCH v4 10/10] Add the drive-reopen command
> 
> > * We could leave it as it is, a distinct command that is not part
> > of
> >   the transaction and that it's closing the old image before
> >   opening
> >   the new one.
> 
> Yes, this would be the short-term preliminary solution. I would tend
> to
> leave it to downstreams to implement it as an extension, though.
> 
> > This is not completely correct, the main intent was to not spread
> > one image chain across two storage domains (making it incomplete if one
> > of them was missing). In the next oVirt release a VM can have
> > different disks on different storage domains, so this wouldn't be a special
> > case but just a normal situation.
> 
> The problem with this kind of argument is that we're not developing
> only for oVirt, but need to look for what makes sense for any management
> tool (or even just direct users of qemu).

That is a general rule, and it's perfectly agreeable, but it has nothing to
do with what I was saying. Do you know any management tool or any reason to
forbid to a VM to have different disks on different storages?
In fact qemu-kvm doesn't even know where the images are coming from (what
particular iscsi connection or what particular NFS server).

What I was asking was a tool (mirroring) to avoid the split of an image
chain between multiple storages, and not a tool to avoid the ability to
have full image chains on different storages.

There is nothing oVirt specific here, it came into play only when I said that
for us is not a problem (for once :-).
Kevin Wolf March 14, 2012, 2:30 p.m. UTC | #9
Am 14.03.2012 14:11, schrieb Eric Blake:
>>> * We could use the transaction APIs without actually making it
>>>   transaction (if we fail in the middle we can't rollback). The only
>>>   advantage of this is that we'd provide a consistent API to libvirt
>>>   and we would postpone the problem to the future. Anyway I strongly
>>>   discourage this as it's completely unsafe and it's going to break
>>>   the transaction semantic. Moreover it's a solution that relies too
>>>   much on the hope of finding something appropriate in the future.
>>
>> This is not an option. Advertising transactional behaviour and not
>> implementing it is just plain wrong.
> 
> Absolutely concur.  From libvirt's perspective, I will be assuming that:
> 
> if 'transaction' exists, then it supports 'blockdev-snapshot-sync' and
> 'drive-mirror' (assuming that these are the only two commands that are
> in 'transaction' in qemu 1.1), but nothing else is safe to use without a
> further probe.

I don't think it's good to conclude alone from the presence of
'transaction' that subcommands exist. Current master does have
'transaction' (and the 'blockdev-snapshot-sync' action), but not yet
'drive-mirror'.

>  Then, if down the road, we go through the pain of making
> 'drive-reopen' transactionable, then there will be some new
> introspection command (one idea was an optional argument to
> query-commands which limits output to just the commands that can also
> occur in a 'transaction'), and libvirt will have to use that
> introspection before using the additional features.

We can have the introspection command now. Either a new option like you
suggest, or even simpler, just add a 'transactionable': 'bool' to the
existing CommandInfo.

>>> * We could leave it as it is, a distinct command that is not part of
>>>   the transaction and that it's closing the old image before opening
>>>   the new one.
>>
>> Yes, this would be the short-term preliminary solution. I would tend to
>> leave it to downstreams to implement it as an extension, though.
> 
> Correct - I'm fine with libvirt using the direct, non-atomic,
> 'drive-reopen' for the immediate needs of oVirt while we work on a more
> complete solution for down the road.

One important difference that probably matters even now is that the
proposed drive-reopen can fail in the middle, where the old BDS is
closed, but the new one couldn't be opened. In this case the disk will
be lost. libvirt must handle this case in some way.

>>> This is not completely correct, the main intent was to not spread one
>>> image chain across two storage domains (making it incomplete if one of
>>> them was missing). In the next oVirt release a VM can have different
>>> disks on different storage domains, so this wouldn't be a special case
>>> but just a normal situation.
>>
>> The problem with this kind of argument is that we're not developing only
>> for oVirt, but need to look for what makes sense for any management tool
>> (or even just direct users of qemu).
> 
> Indeed - that's why I still think it's dangerous to not have an atomic
> 'drive-reopen', even if oVirt can be coded to work in spite of an
> initial implementation being non-atomic.  But there's a difference
> between wish-lists (atomic reopen) and practicality (what can we
> implement now), and I'm not going to insist on the impossible :)

Well, at least upstream you always have the option to reject a feature
instead of taking something broken. And I'm still not quite sure whether
to use this option with part of this series or not.

Kevin
diff mbox

Patch

diff --git a/blockdev.c b/blockdev.c
index 2958419..df8ce14 100644
--- a/blockdev.c
+++ b/blockdev.c
@@ -646,6 +646,69 @@  void do_commit(Monitor *mon, const QDict *qdict)
     }
 }
 
+static void change_blockdev_image(const char *device, const char *new_image_file,
+                                  const char *format, Error **errp)
+{
+    BlockDriverState *bs;
+    BlockDriver *drv, *old_drv, *proto_drv;
+    int ret = 0;
+    int flags;
+    char old_filename[1024];
+
+    bs = bdrv_find(device);
+    if (!bs) {
+        error_set(errp, QERR_DEVICE_NOT_FOUND, device);
+        return;
+    }
+    if (bdrv_in_use(bs)) {
+        error_set(errp, QERR_DEVICE_IN_USE, device);
+        return;
+    }
+
+    pstrcpy(old_filename, sizeof(old_filename), bs->filename);
+
+    old_drv = bs->drv;
+    flags = bs->open_flags;
+
+    drv = bdrv_find_format(format);
+    if (!drv) {
+        error_set(errp, QERR_INVALID_BLOCK_FORMAT, format);
+        return;
+    }
+
+    proto_drv = bdrv_find_protocol(new_image_file);
+    if (!proto_drv) {
+        error_set(errp, QERR_INVALID_BLOCK_FORMAT, format);
+        return;
+    }
+
+    bdrv_drain_all();
+    bdrv_flush(bs);
+
+    bdrv_close(bs);
+    ret = bdrv_open(bs, new_image_file, flags, drv);
+    /*
+     * If reopening the image file we just created fails, fall back
+     * and try to re-open the original image. If that fails too, we
+     * are in serious trouble.
+     */
+    if (ret != 0) {
+        ret = bdrv_open(bs, old_filename, flags, old_drv);
+        if (ret != 0) {
+            error_set(errp, QERR_OPEN_FILE_FAILED, old_filename);
+        } else {
+            error_set(errp, QERR_OPEN_FILE_FAILED, new_image_file);
+        }
+    }
+}
+
+void qmp_drive_reopen(const char *device, const char *new_image_file,
+                      bool has_format, const char *format, Error **errp)
+{
+    change_blockdev_image(device, new_image_file,
+                          has_format ? format : "qcow2", errp);
+}
+
 static void blockdev_do_action(int kind, void *data, Error **errp)
 {
     BlockdevAction action;
diff --git a/hmp-commands.hx b/hmp-commands.hx
index 9c49c33..4afde71 100644
--- a/hmp-commands.hx
+++ b/hmp-commands.hx
@@ -925,6 +925,22 @@  using the specified target.
 ETEXI
 
     {
+        .name       = "drive_reopen",
+        .args_type  = "device:B,new-image-file:s,format:s?",
+        .params     = "device new-image-file [format]",
+        .help       = "Assigns a new image file to a device.\n\t\t\t"
+                      "The image will be opened using the format\n\t\t\t"
+                      "specified or 'qcow2' by default.\n\t\t\t",
+        .mhandler.cmd = hmp_drive_reopen,
+    },
+
+STEXI
+@item drive_reopen
+@findex drive_reopen
+Assigns a new image file to a device.
+ETEXI
+
+    {
         .name       = "drive_add",
         .args_type  = "pci_addr:s,opts:s",
         .params     = "[[<domain>:]<bus>:]<slot>\n"
diff --git a/hmp.c b/hmp.c
index e706db9..ec41d83 100644
--- a/hmp.c
+++ b/hmp.c
@@ -738,6 +738,17 @@  void hmp_snapshot_blkdev(Monitor *mon, const QDict *qdict)
     hmp_handle_error(mon, &errp);
 }
 
+void hmp_drive_reopen(Monitor *mon, const QDict *qdict)
+{
+    const char *device = qdict_get_str(qdict, "device");
+    const char *filename = qdict_get_str(qdict, "new-image-file");
+    const char *format = qdict_get_try_str(qdict, "format");
+    Error *errp = NULL;
+
+    qmp_drive_reopen(device, filename, !!format, format, &errp);
+    hmp_handle_error(mon, &errp);
+}
+
 void hmp_migrate_cancel(Monitor *mon, const QDict *qdict)
 {
     qmp_migrate_cancel(NULL);
diff --git a/hmp.h b/hmp.h
index 5352f00..648a84f 100644
--- a/hmp.h
+++ b/hmp.h
@@ -49,6 +49,7 @@  void hmp_balloon(Monitor *mon, const QDict *qdict);
 void hmp_block_resize(Monitor *mon, const QDict *qdict);
 void hmp_snapshot_blkdev(Monitor *mon, const QDict *qdict);
 void hmp_drive_mirror(Monitor *mon, const QDict *qdict);
+void hmp_drive_reopen(Monitor *mon, const QDict *qdict);
 void hmp_migrate_cancel(Monitor *mon, const QDict *qdict);
 void hmp_migrate_set_downtime(Monitor *mon, const QDict *qdict);
 void hmp_migrate_set_speed(Monitor *mon, const QDict *qdict);
diff --git a/qapi-schema.json b/qapi-schema.json
index 4cebf78..21eb256 100644
--- a/qapi-schema.json
+++ b/qapi-schema.json
@@ -1235,6 +1235,28 @@ 
             '*mode': 'NewImageMode'} }
 
 ##
+# @drive-reopen
+#
+# Assigns a new image file to a device.
+#
+# @device: the name of the device for which we are changing the image file.
+#
+# @new-image-file: the target of the new image. If the file doesn't exists the
+#                  command will fail.
+#
+# @format: #optional the format of the new image, default is 'qcow2'.
+#
+# Returns: nothing on success
+#          If @device is not a valid block device, DeviceNotFound
+#          If @new-image-file can't be opened, OpenFileFailed
+#          If @format is invalid, InvalidBlockFormat
+#
+# Since 1.1
+##
+{ 'command': 'drive-reopen',
+  'data': { 'device': 'str', 'new-image-file': 'str', '*format': 'str' } }
+
+##
 # @human-monitor-command:
 #
 # Execute a command on the human monitor and return the output.
diff --git a/qmp-commands.hx b/qmp-commands.hx
index 122ebe7..7fc30c2 100644
--- a/qmp-commands.hx
+++ b/qmp-commands.hx
@@ -818,6 +818,36 @@  Example:
 EQMP
 
     {
+        .name       = "drive-reopen",
+        .args_type  = "device:B,new-image-file:s,format:s?",
+        .mhandler.cmd_new = qmp_marshal_input_drive_reopen,
+    },
+
+SQMP
+drive-reopen
+------------
+
+Assigns a new image file to a device. Except extremely rare cases where the
+guest is expecting the drive to change its content, the new image should
+contain the same data of the current one.  One use case is to terminate
+a drive-mirror command.
+
+Arguments:
+
+- "device": device name to operate on (json-string)
+- "new-image-file": name of new image file (json-string)
+- "format": format of new image (json-string, optional)
+
+Example:
+
+-> { "execute": "drive-reopen", "arguments": {"device": "ide-hd0",
+                                    "new-image-file": "/some/place/my-image",
+                                    "format": "qcow2" } }
+<- { "return": {} }
+
+EQMP
+
+    {
         .name       = "balloon",
         .args_type  = "value:M",
         .mhandler.cmd_new = qmp_marshal_input_balloon,