support for live VM snapshots / new salt / openstack functions
PROD-17559
Change-Id: I828db6e310946bf9355799987264376637f7ccbc
diff --git a/src/com/mirantis/mk/Virsh.groovy b/src/com/mirantis/mk/Virsh.groovy
new file mode 100644
index 0000000..c0314cd
--- /dev/null
+++ b/src/com/mirantis/mk/Virsh.groovy
@@ -0,0 +1,147 @@
+package com.mirantis.mk
+
+/**
+ *
+ * Virsh functions
+ *
+ */
+
+/**
+ * Ensures that the live snapshot exists
+ *
+ * @param nodeProvider KVM node that hosts the VM
+ * @param target Unique identification of the VM being snapshoted without domain (for ex. ctl01)
+ * @param snapshotName Snapshot name
+ * @param path Path where snapshot image and dumpxml are being put
+ * @param diskName Disk name of the snapshot
+ */
+def liveSnapshotPresent(master, nodeProvider, target, snapshotName, path='/var/lib/libvirt/images', diskName='vda') {
+ def salt = new com.mirantis.mk.Salt()
+ def common = new com.mirantis.mk.Common()
+ def snapshotPresent = ""
+ def domain = salt.getDomainName(master)
+ try {
+ snapshotPresent = salt.getReturnValues(salt.cmdRun(master, "${nodeProvider}*", "virsh snapshot-list ${target}.${domain} | grep ${snapshotName}")).split("\n")[0]
+ } catch (Exception er) {
+ common.infoMsg('snapshot not present')
+ }
+ if (!snapshotPresent.contains(snapshotName)) {
+ def dumpxmlPresent = ''
+ try {
+ dumpxmlPresent = salt.getReturnValues(salt.cmdRun(master, "${nodeProvider}*", "ls -la ${path}/${target}.${domain}.xml")).split("\n")[0]
+ } catch (Exception er) {
+ common.infoMsg('dumpxml file not present')
+ }
+ if (!dumpxmlPresent?.trim()) {
+ salt.cmdRun(master, "${nodeProvider}*", "virsh dumpxml ${target}.${domain} > ${path}/${target}.${domain}.xml")
+ }
+ salt.cmdRun(master, "${nodeProvider}*", "virsh snapshot-create-as --domain ${target}.${domain} ${snapshotName} --diskspec ${diskName},file=${path}/${target}.${domain}.${snapshotName}.qcow2 --disk-only --atomic")
+ }
+}
+
+/**
+ * Ensures that the live snapshot does not exist
+ *
+ * @param nodeProvider KVM node that hosts the VM
+ * @param target Unique identification of the VM being snapshoted without domain (for ex. ctl01)
+ * @param snapshotName Snapshot name
+ * @param path Path where snapshot image and dumpxml are being put
+ */
+def liveSnapshotAbsent(master, nodeProvider, target, snapshotName, path='/var/lib/libvirt/images') {
+ def salt = new com.mirantis.mk.Salt()
+ def common = new com.mirantis.mk.Common()
+ def domain = salt.getDomainName(master)
+ try {
+ salt.cmdRun(master, "${nodeProvider}*", "virsh snapshot-delete ${target}.${domain} --metadata ${snapshotName}")
+ } catch (Exception e) {
+ common.warningMsg('Snapshot ${snapshotName} for ${target}.${domain} does not exist or failed to be removed')
+ }
+ try {
+ salt.runSaltProcessStep(master, "${nodeProvider}*", 'file.remove', ["${path}/${target}.${domain}.${snapshotName}.qcow2"], null, true)
+ } catch (Exception e) {
+ common.warningMsg('Snapshot ${snapshotName} qcow2 file for ${target}.${domain} does not exist or failed to be removed')
+ }
+ try {
+ salt.runSaltProcessStep(master, "${nodeProvider}*", 'file.remove', ["${path}/${target}.${domain}.xml"], null, true)
+ } catch (Exception e) {
+ common.warningMsg('Dumpxml file for ${target}.${domain} does not exist or failed to be removed')
+ }
+}
+
+/**
+ * Rollback
+ *
+ * @param nodeProvider KVM node that hosts the VM
+ * @param target Unique identification of the VM being snapshoted without domain (for ex. ctl01)
+ * @param snapshotName Snapshot name
+ * @param path Path where snapshot image and dumpxml are being put
+ */
+def liveSnapshotRollback(master, nodeProvider, target, snapshotName, path='/var/lib/libvirt/images') {
+ def salt = new com.mirantis.mk.Salt()
+ def common = new com.mirantis.mk.Common()
+ def domain = salt.getDomainName(master)
+ salt.runSaltProcessStep(pepperEnv, "${nodeProvider}*", 'virt.destroy', ["${target}.${domain}"], null, true)
+ salt.cmdRun(pepperEnv, "${nodeProvider}*", "virsh define ${path}/${target}.${domain}.xml")
+ liveSnapshotAbsent(master, nodeProvider, target, snapshotName, path)
+ salt.runSaltProcessStep(pepperEnv, "${nodeProvider}*", 'virt.start', ["${target}.${domain}"], null, true)
+}
+
+/**
+ * Merge snapshot while instance is running
+ *
+ * @param nodeProvider KVM node that hosts the VM
+ * @param target Unique identification of the VM being snapshoted without domain (for ex. ctl01)
+ * @param snapshotName Snapshot name
+ * @param path Path where snapshot image and dumpxml are being put
+ * @param diskName Disk name of the snapshot
+ */
+def liveSnapshotMerge(master, nodeProvider, target, snapshotName, path='/var/lib/libvirt/images', diskName='vda') {
+ def salt = new com.mirantis.mk.Salt()
+ def common = new com.mirantis.mk.Common()
+ def domain = salt.getDomainName(master)
+ try {
+ salt.cmdRun(pepperEnv, "${nodeProvider}*", "virsh blockcommit ${target}.${domain} ${diskName} --active --verbose --pivot")
+ try {
+ salt.cmdRun(pepperEnv, "${nodeProvider}*", "virsh snapshot-delete ${target}.${domain} --metadata ${snapshotName}")
+ } catch (Exception e) {
+ common.warningMsg('Snapshot ${snapshotName} for ${target}.${domain} does not exist or failed to be removed')
+ }
+ try {
+ salt.runSaltProcessStep(pepperEnv, "${nodeProvider}*", 'file.remove', ["${path}/${target}.${domain}.${snapshotName}.qcow2"], null, true)
+ } catch (Exception e) {
+ common.warningMsg('Snapshot ${snapshotName} qcow2 file for ${target}.${domain} does not exist or failed to be removed')
+ }
+ try {
+ salt.runSaltProcessStep(pepperEnv, "${nodeProvider}*", 'file.remove', ["${path}/${target}.${domain}.xml"], null, true)
+ } catch (Exception e) {
+ common.warningMsg('Dumpxml file for ${target}.${domain} does not exist or failed to be removed')
+ }
+ } catch (Exception e) {
+ common.errorMsg("The live snapshoted VM ${target}.${domain} failed to be merged, trying to fix it")
+ checkLiveSnapshotMerge(master, nodeProvider, target, snapshotName, path, diskName)
+ }
+}
+
+
+/**
+ * Check live snapshot merge failure due to known qemu issue not receiving message about merge completion
+ *
+ * @param nodeProvider KVM node that hosts the VM
+ * @param target Unique identification of the VM being snapshoted without domain (for ex. ctl01)
+ * @param snapshotName Snapshot name
+ * @param path Path where snapshot image and dumpxml are being put
+ * @param diskName Disk name of the snapshot
+ */
+def checkLiveSnapshotMerge(master, nodeProvider, target, snapshotName, path='/var/lib/libvirt/images', diskName='vda') {
+ def salt = new com.mirantis.mk.Salt()
+ def domain = salt.getDomainName(master)
+ def out = salt.getReturnValues(salt.cmdRun(pepperEnv, "${nodeProvider}*", "virsh blockjob ${target}.${domain} ${diskName} --info"))
+ if (out.contains('Block Commit')) {
+ def blockJobs = salt.getReturnValues(salt.cmdRun(pepperEnv, "{nodeProvider}*", "virsh qemu-monitor-command ${target}.${domain} --pretty -- '{ \"execute\": \"query-block-jobs\" }'"))
+ if (blockJobs.contains('offset')) {
+ // if Block Commit hangs on 100 and check offset - len = 0, then it is safe to merge the image
+ input message: "Please check if offset - len = 0, If so run: virsh qemu-monitor-command ${target}.${domain} --pretty -- '{ \"execute\": \"block-job-complete\", \"arguments\": { \"device\": \"drive-virtio-disk0\" } }', then virsh define ${path}/${target}.${domain}.xml, then virsh snapshot-delete ${target}.${domain} --metadata ${snapshotName} and remove ${path}/${target}.${domain}.${snapshotName}.qcow2 file. When you resolve this issue click on PROCEED."
+ }
+ }
+}
+