Add an option for virsh undefine command, to remove associated storage
volumes while undefining a domain. This patch allows the user to remove
associated (libvirt managed ) storage volumes while undefining a domain.
The new option --storage for the undefine command takes a string
argument that consists of comma separated list of target or source path
of volumes to be undefined. Volumes are removed after the domain has
been successfully undefined,
If a volume is not part of a storage pool, the user is warned to remove
the volume in question himself.
Option --wipe-storage may be specified along with this, that ensures
the image is wiped before removing.
Option --remove-all-storage enables the user to remove all storage. The
name is chosen long as the users should be aware what they're about to
do.
---
Changes to v3:
- fix ton'o'typos (I should install a spell checker.)
- add a new variable to hold copy of argument to avoid confusion,
typecasting ...
- break volume string into tokens just once and store it in an array
- add both target and source as volume description
( both are needed, as the user should know which volume he shoud
delete, if the domain vanishes )
- reformat argument string in man
- add example containing source path
tools/virsh.c | 180 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
tools/virsh.pod | 18 ++++++
2 files changed, 198 insertions(+), 0 deletions(-)
diff --git a/tools/virsh.c b/tools/virsh.c
index f6571f7..84b5e61 100644
--- a/tools/virsh.c
+++ b/tools/virsh.c
@@ -1924,6 +1924,13 @@ static const vshCmdInfo info_undefine[] = {
static const vshCmdOptDef opts_undefine[] = {
{"domain", VSH_OT_DATA, VSH_OFLAG_REQ, N_("domain name or
uuid")},
{"managed-save", VSH_OT_BOOL, 0, N_("remove domain managed state
file")},
+ {"storage", VSH_OT_DATA, VSH_OFLAG_NONE,
+ N_("remove associated storage volumes (comma separated list of targets "
+ "or source paths) (see domblklist)")},
+ {"remove-all-storage", VSH_OT_BOOL, 0,
+ N_("remove all associated storage volumes (use with caution)")},
+ {"wipe-storage", VSH_OT_BOOL, VSH_OFLAG_NONE,
+ N_("wipe data on the removed volumes")},
{"snapshots-metadata", VSH_OT_BOOL, 0,
N_("remove all domain snapshot metadata, if inactive")},
{NULL, 0, 0, NULL}
@@ -1940,6 +1947,9 @@ cmdUndefine(vshControl *ctl, const vshCmd *cmd)
/* User-requested actions. */
bool managed_save = vshCommandOptBool(cmd, "managed-save");
bool snapshots_metadata = vshCommandOptBool(cmd, "snapshots-metadata");
+ bool wipe_storage = vshCommandOptBool(cmd, "wipe-storage");
+ bool remove_storage = false;
+ bool remove_all_storage = vshCommandOptBool(cmd, "remove-all-storage");
/* Positive if these items exist. */
int has_managed_save = 0;
int has_snapshots_metadata = 0;
@@ -1949,6 +1959,24 @@ cmdUndefine(vshControl *ctl, const vshCmd *cmd)
bool snapshots_safe = false;
int rc = -1;
int running;
+ /* list of volumes to remove along with this domain */
+ const char *volumes_arg = NULL;
+ char *volumes = NULL;
+ char **volume_tokens = NULL;
+ char *volume_tok = NULL;
+ int nvolume_tokens = 0;
+ char *def = NULL;
+ char *source = NULL;
+ char *target = NULL;
+ int vol_i;
+ int tok_i;
+ xmlDocPtr doc = NULL;
+ xmlXPathContextPtr ctxt = NULL;
+ xmlNodePtr *vol_nodes = NULL;
+ int nvolumes = 0;
+ virStorageVolPtr vol = NULL;
+ bool vol_del_failed = false;
if (managed_save) {
flags |= VIR_DOMAIN_UNDEFINE_MANAGED_SAVE;
@@ -1965,6 +1993,17 @@ cmdUndefine(vshControl *ctl, const vshCmd *cmd)
if (!(dom = vshCommandOptDomain(ctl, cmd, &name)))
return false;
+ /* check if a string that should contain list of volumes to remove is present */
+ if (vshCommandOptString(cmd, "storage", &volumes_arg) > 0) {
+ volumes = vshStrdup(ctl, volumes_arg);
+
+ if (remove_all_storage) {
+ vshError(ctl, _("Specified both --storage and
--remove-all-storage"));
+ goto cleanup;
+ }
+ remove_storage = true;
+ }
+
/* Do some flag manipulation. The goal here is to disable bits
* from flags to reduce the likelihood of a server rejecting
* unknown flag bits, as well as to track conditions which are
@@ -2027,6 +2066,20 @@ cmdUndefine(vshControl *ctl, const vshCmd *cmd)
snapshots_safe = true;
}
+ /* Stash domain description for later use */
+ if (remove_storage || remove_all_storage) {
+ if (running) {
+ vshError(ctl, _("Storage volume deletion is supported only on stopped
domains"));
+ goto cleanup;
+ }
+
+ if (!(def = virDomainGetXMLDesc(dom, 0))) {
+ vshError(ctl, _("Could not retrieve domain XML description"));
+ goto cleanup;
+ }
+ }
+
/* Generally we want to try the new API first. However, while
* virDomainUndefineFlags was introduced at the same time as
* VIR_DOMAIN_UNDEFINE_MANAGED_SAVE in 0.9.4, the
@@ -2076,9 +2129,138 @@ out:
ret = true;
} else {
vshError(ctl, _("Failed to undefine domain %s"), name);
+ goto cleanup;
+ }
+
+ /* try to undefine storage volumes associated with this domain, if it's requested
*/
+ if (remove_storage || remove_all_storage) {
+ ret = false;
+
+ /* tokenize the string from user and save it's parts into an array */
+ if (volumes) {
+ /* count the delimiters */
+ volume_tok = volumes;
+ nvolume_tokens = 1; /* we need at least one member */
+ while (*volume_tok) {
+ if (*volume_tok == ',')
+ nvolume_tokens++;
+ volume_tok++;
+ }
+
+ volume_tokens = vshCalloc(ctl, nvolume_tokens, sizeof(char *));
+
+ /* tokenize the input string */
+ nvolume_tokens = 0;
+ volume_tok = volumes;
+ do {
+ volume_tokens[nvolume_tokens] = strsep(&volume_tok, ",");
+ nvolume_tokens++;
+ } while (volume_tok);
+ }
+
+ doc = virXMLParseStringCtxt(def, _("(domain_definition)"), &ctxt);
+ if (!doc)
+ goto cleanup;
+
+ nvolumes = virXPathNodeSet("./devices/disk", ctxt, &vol_nodes);
+
+ if (nvolumes < 0)
+ goto cleanup;
+
+ for (vol_i = 0; vol_i < nvolumes; vol_i++) {
+ ctxt->node = vol_nodes[vol_i];
+ VIR_FREE(target);
+ VIR_FREE(source);
+ if (vol) {
+ virStorageVolFree(vol);
+ vol = NULL;
+ }
+
+ /* get volume source and target paths */
+ if (!(target = virXPathString("string(./target/@dev)", ctxt))) {
+ vshError(ctl, _("Failed to enumerate devices"));
+ goto cleanup;
+ }
+
+ if (!(source = virXPathString("string("
+ "./source/@file|"
+ "./source/@dir|"
+ "./source/@name|"
+ "./source/@dev)", ctxt)) &&
+ virGetLastError())
+ goto cleanup;
+
+ /* lookup if volume was selected by user */
+ if (volumes) {
+ volume_tok = NULL;
+ for (tok_i = 0; tok_i < nvolume_tokens; tok_i++) {
+ if (volume_tokens[tok_i] &&
+ (STREQ_NULLABLE(volume_tokens[tok_i], target) ||
+ STREQ_NULLABLE(volume_tokens[tok_i], source))) {
+ volume_tok = volume_tokens[tok_i];
+ volume_tokens[tok_i] = NULL;
+ break;
+ }
+ }
+ if (!volume_tok)
+ continue;
+ }
+
+ if (!source)
+ continue;
+
+ if (!(vol = virStorageVolLookupByPath(ctl->conn, source))) {
+ vshPrint(ctl,
+ _("Storage volume '%s'(%s) is not managed by
libvirt. "
+ "Remove it manually.\n"), target, source);
+ virResetLastError();
+ continue;
+ }
+
+ if (wipe_storage) {
+ vshPrint(ctl, _("Wiping volume '%s'(%s) ... "), target,
source);
+ fflush(stdout);
+ if (virStorageVolWipe(vol, 0) < 0) {
+ vshError(ctl, _("Failed! Volume not removed."));
+ vol_del_failed = true;
+ continue;
+ } else {
+ vshPrint(ctl, _("Done.\n"));
+ }
+ }
+
+ /* delete the volume */
+ if (virStorageVolDelete(vol, 0) < 0) {
+ vshError(ctl, _("Failed to remove storage volume
'%s'(%s)"),
+ target, source);
+ vol_del_failed = true;
+ }
+ vshPrint(ctl, _("Volume '%s' removed.\n"),
volume_tok?volume_tok:source);
+ }
+
+ /* print volumes specified by user that were not found in domain definition */
+ if (volumes) {
+ for (tok_i = 0; tok_i < nvolume_tokens; tok_i++) {
+ if (volume_tokens[tok_i])
+ vshPrint(ctl, _("Volume '%s' was not found in
domain's "
+ "definition.\n"),
+ volume_tokens[tok_i]);
+ }
+ }
+
+ if (!vol_del_failed)
+ ret = true;
}
cleanup:
+ VIR_FREE(source);
+ VIR_FREE(target);
+ VIR_FREE(volumes);
+ VIR_FREE(volume_tokens);
+ VIR_FREE(def);
+ VIR_FREE(vol_nodes);
+ xmlFreeDoc(doc);
+ xmlXPathFreeContext(ctxt);
virDomainFree(dom);
return ret;
}
diff --git a/tools/virsh.pod b/tools/virsh.pod
index c468f13..3976166 100644
--- a/tools/virsh.pod
+++ b/tools/virsh.pod
@@ -1181,6 +1181,7 @@ Output the device used for the TTY console of the domain. If the
information
is not available the processes will provide an exit code of 1.
=item B<undefine> I<domain-id> [I<--managed-save>]
[I<--snapshots-metadata>]
+[I<--storage> B<volumes> | I<--remove-all-storage>
I<--wipe-storage>]
Undefine a domain. If the domain is running, this converts it to a
transient domain, without stopping it. If the domain is inactive,
@@ -1196,6 +1197,23 @@ domain. Without the flag, attempts to undefine an inactive domain
with
snapshot metadata will fail. If the domain is active, this flag is
ignored.
+The I<--storage> flag takes a parameter B<volumes>, that is a comma
separated
+list of volume target names or source paths of storage volumes to be removed
+along with the undefined domain. Volumes can be undefined and thus removed only
+on inactive domains. Volume deletion is only attempted after the domain is
+undefined; if not all of the requested volumes could be deleted, the
+error message indicates what still remains behind. If a volume path is not
+found in the domain definition, it's treated as if the volume was successfully
+deleteted.
+(See B<domblklist> for list of target names associated to a domain).
+Example: --storage vda,/path/to/storage.img
+
+The I<--remove-all-storage> flag specifies that all of the domain's storage
+volumes should be deleted.
+
+The flag I<--wipe-storage> specifies that the storage volumes should be
+wiped before removal.
+
NOTE: For an inactive domain, the domain name or UUID must be used as the
I<domain-id>.
--
1.7.3.4