Adds an option to virsh undefine command to undefine managed storage volumes along with (inactive) domains. Storage volumes are enumerated and the user may interactivly choose volumes to delete. Unmanaged volumes are listed and the user shall delete them manualy. --- I marked this as a RFC because I am concerned about my "naming scheme" of the added parameters. I couldn't decide which of the following "volumes/storage/disks/..." to use. I'd appreciate your comments on this. This is my second approach to this problem after I got some really good critique from Eric, Daniel and Dave. The user has the choice to activate an interactive mode, that allows to select on a per-device basis which volumes/disks to remove along with the domain. To avoid possible problems, I only allowed to remove storage for inactive domains and unmanaged images (which sidetracked me a lot on my previous attempt) are left to a action of the user. (the user is notified about any unmanaged image for the domain). My next concern is about interactive of the user. I tried to implement a boolean query function, but I'd like to know if I took the right path, as I couldn't find any example in virsh from which I could learn. Thanks for your comments (and time) :) Peter Krempa tools/virsh.c | 265 +++++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 files changed, 259 insertions(+), 6 deletions(-) diff --git a/tools/virsh.c b/tools/virsh.c index 61f69f0..3795d2b 100644 --- a/tools/virsh.c +++ b/tools/virsh.c @@ -295,6 +295,9 @@ static int vshCommandOptULongLong(const vshCmd *cmd, const char *name, static bool vshCommandOptBool(const vshCmd *cmd, const char *name); static const vshCmdOpt *vshCommandOptArgv(const vshCmd *cmd, const vshCmdOpt *opt); +static int vshInteractiveBoolPrompt(vshControl *ctl, + const char *prompt, + bool *confirm); #define VSH_BYID (1 << 1) #define VSH_BYUUID (1 << 2) @@ -1422,6 +1425,8 @@ 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")}, + {"disks", VSH_OT_BOOL, 0, N_("remove associated disk images managed in storage pools (interactive)")}, + {"disks-all", VSH_OT_BOOL, 0, N_("remove all associated disk images managed in storage pools")}, {NULL, 0, 0, NULL} }; @@ -1434,9 +1439,25 @@ cmdUndefine(vshControl *ctl, const vshCmd *cmd) int id; int flags = 0; int managed_save = vshCommandOptBool(cmd, "managed-save"); + int remove_disks = vshCommandOptBool(cmd, "disks"); + int remove_all_disks = vshCommandOptBool(cmd, "disks-all"); int has_managed_save = 0; int rc = -1; + char *domxml; + xmlDocPtr xml = NULL; + xmlXPathContextPtr ctxt = NULL; + xmlXPathObjectPtr obj = NULL; + xmlNodePtr cur = NULL; + int i = 0; + char *source = NULL; + char *target = NULL; + char *type = NULL; + xmlBufferPtr xml_buf = NULL; + virStorageVolPtr volume = NULL; + int state; + bool confirm = false; + if (managed_save) flags |= VIR_DOMAIN_UNDEFINE_MANAGED_SAVE; @@ -1475,15 +1496,172 @@ cmdUndefine(vshControl *ctl, const vshCmd *cmd) } } - if (flags == -1) { - if (has_managed_save == 1) { + + if (flags == -1 && has_managed_save == 1) { + vshError(ctl, + _("Refusing to undefine while domain managed save " + "image exists")); + virDomainFree(dom); + return false; + } + + if (remove_disks || remove_all_disks) { + if ((state = vshDomainState(ctl, dom, NULL)) < 0) { + vshError(ctl, _("Failed to get domain state")); + goto disk_error; + } + + /* removal of storage is possible only for inactive domains */ + if (!((state == VIR_DOMAIN_SHUTOFF) || + (state == VIR_DOMAIN_CRASHED))) { vshError(ctl, - _("Refusing to undefine while domain managed save " - "image exists")); - virDomainFree(dom); - return false; + _("Domain needs to be inactive to delete it with associated storage")); + goto disk_error; + } + + if (remove_disks && !ctl->imode) { + vshError(ctl, "%s\n", _("Option --disks is available only in interactive mode")); + goto disk_error; + } + + domxml = virDomainGetXMLDesc(dom, 0); + if (!domxml) { + vshError(ctl, "%s", _("Failed to get disk information")); + goto disk_error; + } + + xml = xmlReadDoc((const xmlChar *) domxml, "domain.xml", NULL, + XML_PARSE_NOENT | + XML_PARSE_NONET | + XML_PARSE_NOWARNING); + VIR_FREE(domxml); + + if (!xml) { + vshError(ctl, "%s", _("Failed to get disk information")); + goto disk_error; + } + + ctxt = xmlXPathNewContext(xml); + if (!ctxt) { + vshError(ctl, "%s", _("Failed to get disk information")); + goto disk_error; + } + + obj = xmlXPathEval(BAD_CAST "/domain/devices/disk", ctxt); + if ((obj == NULL) || (obj->type != XPATH_NODESET) || + (obj->nodesetval == NULL)) { + vshError(ctl, "%s", _("Failed to get disk information")); + goto disk_error; + } + + for (i = 0; i < obj->nodesetval->nodeNr; i++) { + cur = obj->nodesetval->nodeTab[i]->children; + + type = virXMLPropString(cur, "device"); + + while (cur != NULL) { + if (cur->type == XML_ELEMENT_NODE) { + if (xmlStrEqual(cur->name, BAD_CAST "target")) + target = virXMLPropString(cur, "dev"); + else if (xmlStrEqual(cur->name, BAD_CAST "source")) + source = virXMLPropString(cur, "file"); + } + cur = cur->next; + } + + if (!source) { + VIR_FREE(target); + VIR_FREE(type); + } + + volume = virStorageVolLookupByPath(ctl->conn, (const char *) source); + if (!volume) { + vshPrint(ctl, "%s %s %s %s\n", + _("Volume: Source:"), (const char *)source, + _("Target:"), (const char *) target); + vshPrint(ctl, _("This volume isn't managed by any storage pool, " + "please delete it manualy\n\n")); + /* remove error indication */ + virFreeError(last_error); + last_error = NULL; + } else { + vshPrint(ctl, "%s %s %s %s\n", + _("Volume: Source:"), (const char *)source, + _("Target:"), (const char *) target); + + if (remove_all_disks) { + confirm = true; + } else { + if (vshInteractiveBoolPrompt(ctl, + _("Do you want to undefine this volume?"), + &confirm) < 0) { + vshError(ctl, _("\nError while geting response from user")); + virStorageVolFree(volume); + goto disk_error; + } + } + + /* removal of volume */ + if (confirm) { + if (virStorageVolDelete(volume, 0) == 0) { + virStorageVolFree(volume); + + vshPrint(ctl, _("Volume deleted\n\n")); + + /* remove definition of volume from xml */ + xml_buf = xmlBufferCreate(); + if (!xml_buf) { + vshPrint(ctl, _("Failed to create XML buffer. " + "If domain undefinition fails, domain will be left in inconsistent state.\n\n")); + goto disk_next; + } + + if (xmlNodeDump(xml_buf, xml, obj->nodesetval->nodeTab[i], 0, 0) < 0) { + vshPrint(ctl, _("Failed to extract XML volume description. " + "If domain undefinition fails, domain will be left in inconsistent state.\n\n")); + + xmlBufferFree(xml_buf); + xml_buf = NULL; + goto disk_next; + } + + if (virDomainDetachDeviceFlags(dom, + (char *) xmlBufferContent(xml_buf), + VIR_DOMAIN_AFFECT_CONFIG) < 0) { + vshPrint(ctl, + _("Failed to remove volume \"%s\" from configuration. " + "If domain undefinition fails, domain will be left in inconsistent state.\n\n"), + source); + + xmlBufferFree(xml_buf); + xml_buf = NULL; + goto disk_next; + } + + xmlBufferFree(xml_buf); + xml_buf = NULL; + + } else { + virStorageVolFree(volume); + + vshError(ctl, _("Failed to delete volume.")); + goto disk_error; + } + } + } + +disk_next: + VIR_FREE(source); + VIR_FREE(target); + VIR_FREE(type); } + xmlXPathFreeObject(obj); + xmlXPathFreeContext(ctxt); + xmlFreeDoc(xml); + } /* end of disk undefine stuff */ + + if (flags == -1) { rc = virDomainUndefine(dom); } else { rc = virDomainUndefineFlags(dom, flags); @@ -1520,6 +1698,21 @@ end: virDomainFree(dom); return ret; + +disk_error: + VIR_FREE(source); + VIR_FREE(target); + VIR_FREE(type); + + xmlXPathFreeObject(obj); + xmlXPathFreeContext(ctxt); + xmlFreeDoc(xml); + xmlBufferFree(xml_buf); + + virDomainFree(dom); + + vshError(ctl, _("\nFailed to undefine domain %s"), name); + return false; } @@ -14736,6 +14929,66 @@ vshReadline (vshControl *ctl, const char *prompt) #endif /* !USE_READLINE */ + +/* + * Propmpt for boolean question (yes/no) + * + * Returns "true" on positive answer, "false" on negative + * -1 on error. + */ +static int +vshInteractiveBoolPrompt(vshControl *ctl, + const char *prompt, + bool *confirm) { + const char *positive = _("yes"); + const char *negative = _("no"); + char *r; + char buff[100]; + int ret = -1; + int len; + int c; + + if (confirm == NULL) + return ret; + + if (!ctl->imode) + return ret; + + while (ret == -1) { + vshPrint(ctl, "%s (%s/%s)? ", prompt, positive, negative); + + r = fgets(buff, sizeof(buff), stdin); + if (r == NULL) + break; + len = strlen(buff); + if (len > 0 && buff[len-1] == '\n') + buff[len-1] = '\0'; + else + /* eat rest of line */ + while ((c = fgetc(stdin) != EOF)) + if (c == '\n') + break; + + /* compare */ + if (STRCASEEQLEN(buff, positive, strlen(positive))) { + ret = 0; + *confirm = true; + break; + } + + if (STRCASEEQLEN(buff, negative, strlen(negative))) { + ret = 0; + *confirm = false; + break; + } + + /* errorneus response - warn and ask again*/ + vshPrint(ctl, "\"%s\" %s\n", buff, _("Response not understood")); + + } + return ret; +} + /* * Deinitialize virsh */ -- 1.7.6 -- libvir-list mailing list libvir-list@xxxxxxxxxx https://www.redhat.com/mailman/listinfo/libvir-list