#!/usr/bin/python # # A plugin for executing script needed by vmops cloud import os, sys, time import XenAPIPlugin sys.path.append("/opt/xensource/sm/") import SR, VDI, SRCommand, util, lvutil from util import CommandException import vhdutil import shutil import lvhdutil import subprocess from lvmcache import LVMCache from journaler import Journaler from lock import Lock import errno import subprocess import xs_errors import cleanup import hostvmstats import socket import stat import random import tempfile VHD_UTIL = '/usr/sbin/vhd-util' VHD_PREFIX = 'VHD-' def echo(fn): def wrapped(*v, **k): name = fn.__name__ util.SMlog("#### VMOPS enter %s ####" % name ) res = fn(*v, **k) util.SMlog("#### VMOPS exit %s ####" % name ) return res return wrapped @echo def create_secondary_storage_folder(session, args): local_mount_path = None util.SMlog("create_secondary_storage_folder, args: " + str(args)) try: try: # Mount the remote resource folder locally remote_mount_path = args["remoteMountPath"] local_mount_path = os.path.join(SR.MOUNT_BASE, "mount" + str(int(random.random() * 1000000))) mount(remote_mount_path, local_mount_path) # Create the new folder new_folder = local_mount_path + "/" + args["newFolder"] if not os.path.isdir(new_folder): current_umask = os.umask(0) os.makedirs(new_folder) os.umask(current_umask) except OSError, (errno, strerror): errMsg = "create_secondary_storage_folder failed: errno: " + str(errno) + ", strerr: " + strerror util.SMlog(errMsg) raise xs_errors.XenError(errMsg) except: errMsg = "create_secondary_storage_folder failed." util.SMlog(errMsg) raise xs_errors.XenError(errMsg) finally: if local_mount_path != None: # Unmount the local folder umount(local_mount_path) # Remove the local folder os.system("rm -rf " + local_mount_path) return "1" @echo def delete_secondary_storage_folder(session, args): local_mount_path = None util.SMlog("delete_secondary_storage_folder, args: " + str(args)) try: try: # Mount the remote resource folder locally remote_mount_path = args["remoteMountPath"] local_mount_path = os.path.join(SR.MOUNT_BASE, "mount" + str(int(random.random() * 1000000))) mount(remote_mount_path, local_mount_path) # Delete the specified folder folder = local_mount_path + "/" + args["folder"] if os.path.isdir(folder): os.system("rm -rf " + folder) except OSError, (errno, strerror): errMsg = "delete_secondary_storage_folder failed: errno: " + str(errno) + ", strerr: " + strerror util.SMlog(errMsg) raise xs_errors.XenError(errMsg) except: errMsg = "delete_secondary_storage_folder failed." util.SMlog(errMsg) raise xs_errors.XenError(errMsg) finally: if local_mount_path != None: # Unmount the local folder umount(local_mount_path) # Remove the local folder os.system("rm -rf " + local_mount_path) return "1" @echo def execute_script(session, args): return "" @echo def post_create_private_template(session, args): local_mount_path = None try: try: # Mount the remote templates folder locally remote_mount_path = args["remoteTemplateMountPath"] local_mount_path = os.path.join(SR.MOUNT_BASE, "template" + str(int(random.random() * 10000))) mount(remote_mount_path, local_mount_path) util.SMlog("Mounted secondary storage template folder") # Retrieve args filename = args["templateFilename"] name = args["templateName"] description = args["templateDescription"] checksum = args["checksum"] virtual_size = args["virtualSize"] template_id = args["templateId"] # Determine the template size template_download_folder = local_mount_path + "/" + args["templateDownloadFolder"] template_download_path = template_download_folder + filename file_size = os.path.getsize(template_download_path) util.SMlog("Got template file_size: " + str(file_size)) # Create the template.properties file template_properties_download_path = template_download_folder + "template.properties" f = open(template_properties_download_path, "w") f.write("filename=" + filename + "\n") f.write("name=" + filename + "\n") f.write("vhd=true\n") f.write("id=" + template_id + "\n") f.write("vhd.filename=" + filename + "\n") f.write("uniquename=" + name + "\n") f.write("vhd.virtualsize=" + virtual_size + "\n") f.write("vhd.size=" + str(file_size) + "\n") f.write("virtualsize=" + virtual_size + "\n") f.write("checksum=" + checksum + "\n") f.write("hvm=true\n") f.write("description=" + name + "\n") f.close() util.SMlog("Created template.properties file") # Create the template install folder if necessary template_install_folder = local_mount_path + "/" + args["templateInstallFolder"] if not os.path.isdir(template_install_folder): current_umask = os.umask(0) os.makedirs(template_install_folder) os.umask(current_umask) # Move the template and the template.properties file to the install folder os.system("mv " + template_download_folder + "/" + filename + " " + template_install_folder) os.system("mv " + template_download_folder + "/template.properties " + template_install_folder) template_install_path = template_install_folder + filename template_properties_install_path = template_install_folder + "template.properties" # Set permissions permissions = stat.S_IREAD | stat.S_IWRITE | stat.S_IRGRP | stat.S_IWGRP | stat.S_IROTH | stat.S_IWOTH os.chmod(template_install_path, permissions) os.chmod(template_properties_install_path, permissions) util.SMlog("Set permissions on template and template.properties") # Delete the template download folder os.system("rm -rf " + template_download_folder) except: errMsg = "post_create_private_template failed." util.SMlog(errMsg) raise xs_errors.XenError(errMsg) finally: if local_mount_path != None: # Unmount the local templates folder umount(local_mount_path) # Remove the local templates folder os.system("rm -rf " + local_mount_path) return "1" def isfile(path, isISCSI): errMsg = '' exists = True if isISCSI: exists = checkVolumeAvailablility(path) else: exists = os.path.isfile(path) if not exists: errMsg = "File " + path + " does not exist." util.SMlog(errMsg) raise xs_errors.XenError(errMsg) return errMsg def copyfile(fromFile, toFile, isISCSI): util.SMlog("Starting to copy " + fromFile + " to " + toFile) errMsg = '' if isISCSI: try: cmd = ['dd', 'if=' + fromFile, 'of=' + toFile] txt = util.pread2(cmd) except: txt = '' errMsg = "Error while copying " + fromFile + " to " + toFile + " in ISCSI mode" util.SMlog(errMsg) raise xs_errors.XenError(errMsg) else: try: shutil.copy2(fromFile, toFile) except OSError, (errno, strerror): errMsg = "Error while copying " + fromFile + " to " + toFile + " with errno: " + str(errno) + " and strerr: " + strerror util.SMlog(errMsg) raise xs_errors.XenError(errMsg) util.SMlog("Successfully copied " + fromFile + " to " + toFile) return errMsg def chdir(path): try: os.chdir(path) except OSError, (errno, strerror): errMsg = "Unable to chdir to " + path + " because of OSError with errno: " + str(errno) + " and strerr: " + strerror util.SMlog(errMsg) raise xs_errors.XenError(errMsg) util.SMlog("Chdired to " + path) return def coalesce(vhdPath): util.SMlog("Starting to coalesce " + vhdPath + " with its parent") try : cmd = [VHD_UTIL, "coalesce", "-n", vhdPath] txt = util.pread2(cmd) except: errMsg = "Unexpected error while trying to coalesce " + vhdPath + " to its parent" util.SMlog(errMsg) raise xs_errors.XenError(errMsg) util.SMlog("Successfully coalesced " + vhdPath + " with its parent ") return def scanParent(path): # Do a scan for the parent for ISCSI volumes # Note that the parent need not be visible on the XenServer parentUUID = '' try: lvName = os.path.basename(path) dirname = os.path.dirname(path) vgName = os.path.basename(dirname) vhdInfo = vhdutil.getVHDInfoLVM(lvName, lvhdutil.extractUuid, vgName) parentUUID = vhdInfo.parentUuid except: errMsg = "Could not get vhd parent of " + path util.SMlog(errMsg) raise xs_errors.XenError(errMsg) return parentUUID def getParent(path, isISCSI): parentUUID = '' try : if isISCSI: parentUUID = vhdutil.getParent(path, lvhdutil.extractUuid) else: parentUUID = vhdutil.getParent(path, cleanup.FileVDI.extractUuid) except: errMsg = "Could not get vhd parent of " + path util.SMlog(errMsg) raise xs_errors.XenError(errMsg) return parentUUID def getParentOfSnapshot(snapshotUuid, primarySRPath, isISCSI): snapshotVHD = getVHD(snapshotUuid, isISCSI) snapshotPath = os.path.join(primarySRPath, snapshotVHD) baseCopyUuid = '' if isISCSI: checkVolumeAvailablility(snapshotPath) baseCopyUuid = scanParent(snapshotPath) else: baseCopyUuid = getParent(snapshotPath, isISCSI) util.SMlog("Base copy of snapshotUuid: " + snapshotUuid + " is " + baseCopyUuid) return baseCopyUuid def setParent(parent, child): try: cmd = [VHD_UTIL, "modify", "-p", parent, "-n", child] txt = util.pread2(cmd) except: errMsg = "Unexpected error while trying to set parent of " + child + " to " + parent util.SMlog(errMsg) raise xs_errors.XenError(errMsg) util.SMlog("Successfully set parent of " + child + " to " + parent) return def rename(originalVHD, newVHD): try: os.rename(originalVHD, newVHD) except OSError, (errno, strerror): errMsg = "OSError while renaming " + origiinalVHD + " to " + newVHD + "with errno: " + str(errno) + " and strerr: " + strerror util.SMlog(errMsg) raise xs_errors.XenError(errMsg) return def coalesceToChild(backupVHD, childUUID, isISCSI): # coalesce childVHD with its parent childVHD = getVHD(childUUID, isISCSI) util.SMlog("childVHD: " + childVHD) # check for existence of childVHD isfile(childVHD, False) util.SMlog("childVHD " + childVHD + " exists") # No exception thrown, file exists coalesce(childVHD) # rename the existing backupVHD file to childVHD # childVHD file automatically gets overwritten rename(backupVHD, childVHD) # parent of the newly coalesced file still remains the same. # child of childVHD has it's parent name still set to childVHD. # So the VHD chain hasn't been broken. return def makedirs(path): if not os.path.isdir(path): try: os.makedirs(path) except OSError, (errno, strerror): errMsg = "OSError while creating " + path + " with errno: " + str(errno) + " and strerr: " + strerror util.SMlog(errMsg) raise xs_errors.XenError(errMsg) return def mount(remoteDir, localDir): makedirs(localDir) try: cmd = ['mount', remoteDir, localDir] txt = util.pread2(cmd) except: txt = '' errMsg = "Unexpected error while trying to mount " + remoteDir + " to " + localDir util.SMlog(errMsg) raise xs_errors.XenError(errMsg) util.SMlog("Successfully mounted " + remoteDir + " to " + localDir) return def umount(localDir): success = False if os.path.isdir(localDir) and os.path.ismount(localDir): try: cmd = ['umount', localDir] util.pread2(cmd) except CommandException: errMsg = "CommandException raised while trying to umount " + localDir util.SMlog(errMsg) raise xs_errors.XenError(errMsg) util.SMlog("Successfully unmounted " + localDir) success = True else: util.SMlog("LocalDir: " + localDir + " doesn't exist or is not a mount point") return def mountTemplatesDir(secondaryStorageMountPath, templatePath): # Aim is to mount / on # SR.MOUNT_BASE/ # It will be unmounted after createVolumeFromSnapshot finishes. # It should be mounted read-only, but don't know how to do that # The random-uuid saves us from conflicts while restoring two different root volumes absoluteTemplatePath = os.path.join(secondaryStorageMountPath, templatePath) absoluteTemplateDir = os.path.dirname(absoluteTemplatePath) randomUUID = util.gen_uuid() localTemplateDir = os.path.join(SR.MOUNT_BASE, randomUUID) # create the temp dir makedirs(localTemplateDir) # mount mount(absoluteTemplateDir, localTemplateDir) return localTemplateDir def mountSnapshotsDir(secondaryStorageMountPath, relativeDir, dcId, accountId, instanceId): # The aim is to mount secondaryStorageMountPath on # SR.MOUNT_BASE// # And create / dir on it, if it doesn't exist already. # Assuming that secondaryStorageMountPath exists remotely # Alex's suggestion and currently implemented: # Just mount secondaryStorageMountPath/ everytime # Never unmount. snapshotsDir = os.path.join(secondaryStorageMountPath, relativeDir) # Mkdir local mount point dir, if it doesn't exist. localMountPointPath = os.path.join(SR.MOUNT_BASE, dcId) localMountPointPath = os.path.join(localMountPointPath, relativeDir) makedirs(localMountPointPath) # if something is not mounted already on localMountPointPath, # mount secondaryStorageMountPath on localMountPath if os.path.ismount(localMountPointPath): # There is only one secondary storage per zone. # And we are mounting each sec storage under a zone-specific directory # So two secondary storage snapshot dirs will never get mounted on the same point on the same XenServer. util.SMlog("The remote snapshots directory has already been mounted on " + localMountPointPath) else: mount(snapshotsDir, localMountPointPath) # Create accountId/instanceId dir on localMountPointPath, if it doesn't exist backupsDir = os.path.join(localMountPointPath, accountId) backupsDir = os.path.join(backupsDir, instanceId) makedirs(backupsDir) return backupsDir @echo def unmountSnapshotsDir(session, args): dcId = args['dcId'] localMountPointPath = os.path.join(SR.MOUNT_BASE, dcId) localMountPointPath = os.path.join(localMountPointPath, "snapshots") try: umount(localMountPointPath) except: util.SMlog("Ignoring the error while trying to unmount the snapshots dir.") return "1" def getPrimarySRPath(primaryStorageSRUuid, isISCSI): if isISCSI: primarySRDir = lvhdutil.VG_PREFIX + primaryStorageSRUuid return os.path.join(lvhdutil.VG_LOCATION, primarySRDir) else: return os.path.join(SR.MOUNT_BASE, primaryStorageSRUuid) def getVHD(UUID, isISCSI): if isISCSI: return VHD_PREFIX + UUID else: return UUID + '.' + SR.DEFAULT_TAP def getIsTrueString(stringValue): booleanValue = False if (stringValue and stringValue == 'true'): booleanValue = True return booleanValue def makeUnavailable(uuid, primarySRPath, isISCSI): VHD = getVHD(uuid, isISCSI) path = os.path.join(primarySRPath, VHD) manageAvailability(path, '-an') return def manageAvailability(path, value): util.SMlog("Setting availability of " + path + " to " + value) try: cmd = ['/usr/sbin/lvchange', value, path] util.pread2(cmd) except: #CommandException, (rc, cmdListStr, stderr): #errMsg = "CommandException thrown while executing: " + cmdListStr + " with return code: " + str(rc) + " and stderr: " + stderr errMsg = "Unexpected exception thrown by lvchange" util.SMlog(errMsg) if value == "-ay": # Raise an error only if we are trying to make it available. # Just warn if we are trying to make it unavailable after the # snapshot operation is done. raise xs_errors.XenError(errMsg) return def checkVolumeAvailablility(path): try: if not isVolumeAvailable(path): # The VHD file is not available on XenSever. The volume is probably # inactive or detached. # Do lvchange -ay to make it available on XenServer manageAvailability(path, '-ay') except: errMsg = "Could not determine status of ISCSI path: " + path util.SMlog(errMsg) raise xs_errors.XenError(errMsg) success = False i = 0 while i < 6: i = i + 1 # Check if the vhd is actually visible by checking for the link # set isISCSI to true success = isVolumeAvailable(path) if success: util.SMlog("Made vhd: " + path + " available and confirmed that it is visible") break # Sleep for 10 seconds before checking again. time.sleep(10) # If not visible within 1 min fail if not success: util.SMlog("Could not make vhd: " + path + " available despite waiting for 1 minute. Does it exist?") return success def isVolumeAvailable(path): # Check if iscsi volume is available on this XenServer. status = "0" try: p = subprocess.Popen(["/bin/bash", "-c", "if [ -L " + path + " ]; then echo 1; else echo 0;fi"], stdout=subprocess.PIPE) status = p.communicate()[0].strip("\n") except: errMsg = "Could not determine status of ISCSI path: " + path util.SMlog(errMsg) raise xs_errors.XenError(errMsg) return (status == "1") def getLastSnapshotUuid(volumeUuid, expectedLastSnapshotUuid): actualLastSnapshotUuid = '' if volumeUuid: cmd = ['xe', 'vdi-param-get', 'uuid=' + volumeUuid, 'param-name=snapshots'] stdout = '' try: stdout = util.pread2(cmd) except: #CommandException, (rc, cmdListStr, stderr): #errMsg = "CommandException thrown while executing: " + cmdListStr + " with return code: " + str(rc) + " and stderr: " + stderr errMsg = "Unexpected error while executing cmd: " + str(cmd) util.SMlog(errMsg) raise xs_errors.XenError(errMsg) if stdout: snapshots = stdout.split(';') if len(snapshots) == 1: if snapshots[0] == expectedLastSnapshotUuid: actualLastSnapshotUuid = expectedLastSnapshotUuid elif len(snapshots) == 2: # We expect only 1 snapshot to be present. If there is another that is unexpected and the last one if (expectedLastSnapshotUuid == snapshots[0].strip()): actualLastSnapshotUuid = snapshots[1].strip() else: # it should be equal to snapshots[1]. Else I have no idea. actualLastSnapshotUuid = snapshots[0].strip() else: # len(snapshots) > 2: errMsg = "Volume: " + volumeUuid + " has more than 2 snapshots: " + str(snapshots) util.SMlog(errMsg) raise xs_errors.XenError(errMsg) return actualLastSnapshotUuid @echo def validateSnapshot(session, args): util.SMlog("Called validateSnapshot with " + str(args)) primaryStorageSRUuid = args['primaryStorageSRUuid'] volumeUuid = args['volumeUuid'] firstBackupUuid = args['firstBackupUuid'] previousSnapshotUuid = args['previousSnapshotUuid'] templateUuid = args['templateUuid'] isISCSI = getIsTrueString(args['isISCSI']) primarySRPath = getPrimarySRPath(primaryStorageSRUuid, isISCSI) util.SMlog("primarySRPath: " + primarySRPath) volumeVHD = getVHD(volumeUuid, isISCSI) volumePath = os.path.join(primarySRPath, volumeVHD) util.SMlog("volumePath: " + volumePath) baseCopyUuid = '' wasVolumeAvailable = True if isISCSI: wasVolumeAvailable = isVolumeAvailable(volumePath) if not wasVolumeAvailable: # make it available checkVolumeAvailablility(volumePath) baseCopyUuid = scanParent(volumePath) else: baseCopyUuid = getParent(volumePath, isISCSI) if baseCopyUuid is None: # Make it an empty string so that it can be used in the return value baseCopyUuid = '' actualSnapshotUuid = getLastSnapshotUuid(volumeUuid, previousSnapshotUuid) expectedActual = firstBackupUuid + "#" + baseCopyUuid + "#" + actualSnapshotUuid if firstBackupUuid: # This is not the first snapshot if baseCopyUuid and (baseCopyUuid == firstBackupUuid): retval = "1#" else: retval = "0#" else: if templateUuid: # The DB thinks this is the first snapshot of a ROOT DISK # The parent of the volume should be the base template, which is also the parent of the given templateUuid. templateVHD = getVHD(templateUuid, isISCSI) templatePath = os.path.join(primarySRPath, templateVHD) baseTemplateUuid = '' wasTemplateAvailable = True if isISCSI: wasTemplateAvailable = isVolumeAvailable(templatePath) if not wasVolumeAvailable: # make it available checkVolumeAvailablility(templatePath) baseTemplateUuid = scanParent(templatePath) else: baseTemplateUuid = getParent(templatePath, isISCSI) if baseTemplateUuid is None: # This will never happen. baseTemplateUuid = '' expectedActual = baseTemplateUuid + "#" + baseCopyUuid + "#" + actualSnapshotUuid if baseTemplateUuid and (baseCopyUuid == baseTemplateUuid): retval = "1#" else: retval = "0#" if isISCSI and not wasTemplateAvailable: manageAvailability(templatePath, '-an') else: # The DB thinks this is the first snapshot of a DATA DISK. # The volume VDI should not have any parent. if not baseCopyUuid: retval = "1#" else: retval = "0#" # Set the volume's visibility back to what it was. if isISCSI and not wasVolumeAvailable: manageAvailability(volumePath, '-an') return retval + expectedActual @echo def backupSnapshot(session, args): util.SMlog("Called backupSnapshot with " + str(args)) primaryStorageSRUuid = args['primaryStorageSRUuid'] dcId = args['dcId'] accountId = args['accountId'] volumeId = args['volumeId'] secondaryStorageMountPath = args['secondaryStorageMountPath'] snapshotUuid = args['snapshotUuid'] prevSnapshotUuid = args['prevSnapshotUuid'] prevBackupUuid = args['prevBackupUuid'] isFirstSnapshotOfRootVolume = getIsTrueString(args['isFirstSnapshotOfRootVolume']) isISCSI = getIsTrueString(args['isISCSI']) primarySRPath = getPrimarySRPath(primaryStorageSRUuid, isISCSI) util.SMlog("primarySRPath: " + primarySRPath) baseCopyUuid = getParentOfSnapshot(snapshotUuid, primarySRPath, isISCSI) baseCopyVHD = getVHD(baseCopyUuid, isISCSI) baseCopyPath = os.path.join(primarySRPath, baseCopyVHD) util.SMlog("Base copy path: " + baseCopyPath) prevBaseCopyUuid = '' if prevSnapshotUuid: prevBaseCopyUuid = getParentOfSnapshot(prevSnapshotUuid, primarySRPath, isISCSI) # Mount secondary storage mount path on XenServer along the path # /var/run/sr-mount//snapshots/ and create / dir # on it. backupsDir = mountSnapshotsDir(secondaryStorageMountPath, "snapshots", dcId, accountId, volumeId) util.SMlog("Backups dir " + backupsDir) # chdir to the backupsDir for convenience chdir(backupsDir) if baseCopyUuid == prevBaseCopyUuid: # There has been no change since the last snapshot so no need to backup util.SMlog("There has been no change since the last snapshot with backup: " + prevBaseCopyUuid) if isFirstSnapshotOfRootVolume: # baseCopyUuid is template. That is *NOT* the backup of any # snapshot. There is no backup. So create an empty dummyVHD representing the empty snapshot. # This will prevent deleteSnapshotBackup and createVolumeFromSnapshot from breaking. prevBaseCopyUuid = createDummyVHD(baseCopyPath, backupsDir, isISCSI) # The backup snapshot is the new dummy VHD created. # Set the uuid of the current backup to that of last backup txt = "1#" + prevBaseCopyUuid return txt # Check existence of snapshot on primary storage isfile(baseCopyPath, isISCSI) # copy baseCopyPath to backupsDir backupFile = os.path.join(backupsDir, baseCopyVHD) copyfile(baseCopyPath, backupFile, isISCSI) # Now set the availability of the snapshotPath and the baseCopyPath to false makeUnavailable(snapshotUuid, primarySRPath, isISCSI) manageAvailability(baseCopyPath, '-an') if prevSnapshotUuid: makeUnavailable(prevSnapshotUuid, primarySRPath, isISCSI) makeUnavailable(prevBaseCopyUuid, primarySRPath, isISCSI) if isFirstSnapshotOfRootVolume: # First snapshot of the root volume. # It's parent is not null, but the template vhd. # Create a dummy empty vhd and set the parent of backupVHD to it. # This will prevent deleteSnapshotBackup and createVolumeFromSnapshot from breaking prevBackupUuid = createDummyVHD(baseCopyPath, backupsDir, isISCSI) # Because the primary storage is always scanned, the parent of this base copy is always the first base copy. # We don't want that, we want a chain of VHDs each of which is a delta from the previous. # So set the parent of the current baseCopyVHD to prevBackupVHD if prevBackupUuid: # If there was a previous snapshot prevBackupVHD = getVHD(prevBackupUuid, isISCSI) setParent(prevBackupVHD, backupFile) txt = "1#" + baseCopyUuid return txt def createDummyVHD(baseCopyPath, backupsDir, isISCSI): dummyUUID = '' try: dummyUUID = util.gen_uuid() dummyVHD = getVHD(dummyUUID, isISCSI) if isISCSI: checkVolumeAvailablility(baseCopyPath) cmd = [VHD_UTIL, 'query', '-v', '-n', baseCopyPath] virtualSizeInMB = util.pread2(cmd) util.SMlog("Virtual size of " + baseCopyPath + " is " + virtualSizeInMB) cmd = [VHD_UTIL, 'create', '-n', dummyVHD, '-s', virtualSizeInMB] util.pread2(cmd) except CommandException: errMsg = "Unexpected error while creating a dummy VHD " + dummyVHD + " on " + backupsDir util.SMlog(errMsg) raise xs_errors.XenError(errMsg) util.SMlog("Successfully created a new dummy VHD: " + dummyVHD + " on " + backupsDir) return dummyUUID @echo def deleteSnapshotBackup(session, args): util.SMlog("Calling deleteSnapshotBackup with " + str(args)) dcId = args['dcId'] accountId = args['accountId'] volumeId = args['volumeId'] secondaryStorageMountPath = args['secondaryStorageMountPath'] backupUUID = args['backupUUID'] childUUID = args['childUUID'] isISCSI = getIsTrueString(args['isISCSI']) backupsDir = mountSnapshotsDir(secondaryStorageMountPath, "snapshots", dcId, accountId, volumeId) # chdir to the backupsDir for convenience chdir(backupsDir) backupVHD = getVHD(backupUUID, isISCSI) util.SMlog("checking existence of " + backupVHD) # The backupVHD is on secondary which is NFS and not ISCSI. if not os.path.isfile(backupVHD): util.SMlog("backupVHD " + backupVHD + "does not exist. Not trying to delete it") return "1" util.SMlog("backupVHD " + backupVHD + " exists.") # Case 1) childUUID exists if childUUID: coalesceToChild(backupVHD, childUUID, isISCSI) else: # Just delete the backupVHD try: os.remove(backupVHD) except OSError, (errno, strerror): errMsg = "OSError while removing " + backupVHD + " with errno: " + str(errno) + " and strerr: " + strerror util.SMlog(errMsg) raise xs_errors.XenError(errMsg) return "1" @echo def createVolumeFromSnapshot(session, args): util.SMlog("Calling createVolumeFromSnapshot with " + str(args)) dcId = args['dcId'] accountId = args['accountId'] volumeId = args['volumeId'] secondaryStorageMountPath = args['secondaryStorageMountPath'] backedUpSnapshotUuid = args['backedUpSnapshotUuid'] templatePath = args['templatePath'] templateDownloadFolder = args['templateDownloadFolder'] isISCSI = getIsTrueString(args['isISCSI']) backupsDir = mountSnapshotsDir(secondaryStorageMountPath, "snapshots", dcId, accountId, volumeId) util.SMlog("Backups dir " + backupsDir) # chdir to the backupsDir for convenience chdir(backupsDir) # Get the parent VHD chain of the backupSnapshotVHD vhdChain = [] uuid = backedUpSnapshotUuid while uuid is not None: util.SMlog("Current uuid in parent chain " + uuid) vhd = getVHD(uuid, isISCSI) vhdChain.append(vhd) uuid = getParent(vhd, isISCSI) util.SMlog("successfully created the parent chain " + str(vhdChain)) destDirParent = '' destDir = '' if templateDownloadFolder: # Copy all the vhds to the final destination templates dir # It is some random directory on the primary created for templates. destDirParent = os.path.join(SR.MOUNT_BASE, "mount" + str(int(random.random() * 1000000))) destDir = os.path.join(destDirParent, templateDownloadFolder) # create the this directory, if it isn't there makedirs(destDir) else: # Copy all the vhds to a temp directory # Create a temp directory destDir = backupsDir + '_temp' # Delete the temp directory if it already exists (from a previous createVolumeFromSnapshot) rmtree(destDir) if templateDownloadFolder: # The destDir was created in create_secondary_storage_folder but is not mounted on the primary. Mount it again. remoteMountPoint = os.path.join(secondaryStorageMountPath, "template"); remoteMountPoint = os.path.join(remoteMountPoint, templateDownloadFolder) mount(remoteMountPoint, destDir) else: # The parent of the destDir is the snapshots dir and is mounted on the primary. Just create the directory structure. makedirs(destDir) # Copy for vhd in vhdChain: vhdPath = os.path.join(backupsDir, vhd) tempFile = os.path.join(destDir, vhd) # We are copying files on secondary storage which is NFS. Set isISCSI to false copyfile(vhdPath, tempFile, False) util.SMlog("Successfully copied all files") # coalesce the vhd chains from bottom to top # chdir to destDir for convenience chdir(destDir) # coalesce i = 0 finalVhd = vhdChain[0] for vhd in vhdChain: finalVhd = vhdChain[i] last = len(vhdChain) - 1 if i == last: # last vhd, has no parent. Don't coalesce break if templatePath and i == (last - 1): # Hack for root disks, the first Vhd is a dummy one. # Do not coalesce the actual disk with the dummy one. # Instead coalesce it with the templateVHD. break i = i + 1 # They are arranged from child to parent. util.SMlog("Starting to coalesce " + vhd + " with its parent") try: cmd = [VHD_UTIL, "coalesce", "-n", vhd] txt = util.pread2(cmd) except: errMsg = "Unexpected error while trying to coalesce " + vhd + " to its parent" util.SMlog(errMsg) raise xs_errors.XenError(errMsg) util.SMlog("Successfully coalesced " + vhd + " with its parent") # Remove the child vhd try: os.remove(vhd) except OSError, (errno, strerror): errMsg = "OSError while removing " + vhd + " with errno: " + str(errno) + " and strerr: " + strerror util.SMlog(errMsg) raise xs_errors.XenError(errMsg) util.SMlog("successfully coalesced all vhds to the parent " + finalVhd) finalVhdPath = os.path.join(destDir, finalVhd) # This vhd has to be introduced with a new uuid because of the VDI UUID # uniqueness constraint newUUID = '' try: newUUID = util.gen_uuid() except: errMsg = "Unexpected error while trying to generate a uuid" util.SMlog(errMsg) raise xs_errors.XenError(errMsg) util.SMlog("generated a uuid " + newUUID) # Now, at the Java layer an NFS SR is created with mount point destDir and scanned. The vhd # file is automatically introduced and a vdi.copy is done to move it to # primary storage. # new vhd file is created on NFS. So it should have NFS naming convention, # set isISCSI to false newVhd = getVHD(newUUID, False) rename(finalVhd, newVhd) # For root disk if templatePath: # This will create a vhd on secondary storage destDir with name newVhd mergeTemplateAndSnapshot(secondaryStorageMountPath, templatePath, destDir, newVhd, isISCSI) # set the hidden flag of the new VHD to false, so that it doesn't get deleted when the SR scan is done. try: vhdutil.setHidden(newVhd, False) except: errMsg = "Unexpected error while trying to set Hidden flag of " + newVhd util.SMlog(errMsg) raise xs_errors.XenError(errMsg) util.SMlog("Successfully set hidden flag of " + newVhd) virtualSizeInMB = 0 try: cmd = [VHD_UTIL, 'query', '-v', '-n', newVhd] virtualSizeInMB = util.pread2(cmd) util.SMlog("Virtual size of " + newVhd + " is " + virtualSizeInMB) except: errMsg = "Unexpected error while trying to get the virtual size of " + newVhd util.SMlog(errMsg) raise xs_errors.XenError(errMsg) if templateDownloadFolder: # We are done with the destDir on the primary, unmount it and delete the random directory. # Current dir is destDir # cd to something else before unmounting chdir(backupsDir) # as good as anything else # unmount what was mounted. umount(destDir) # Remove the tree starting from the mountXXXX part, just the directories rmtree(destDirParent) # The coalesced data is still available on the secondary. return "1#" + newUUID + "#" + virtualSizeInMB def mergeTemplateAndSnapshot(secondaryStorageMountPath, templatePath, destDir, newVhd, isISCSI): # Mount the template directory present on the secondary to the primary templateDirOnPrimary = mountTemplatesDir(secondaryStorageMountPath, templatePath) # Current dir is destDir templateVHD = os.path.basename(templatePath) templatePathOnPrimary = os.path.join(templateDirOnPrimary, templateVHD) templatePathOnTemp = os.path.join(destDir, templateVHD) # Copying from secondary to secondary, so set ISCSI to False copyfile(templatePathOnPrimary, templatePathOnTemp, False) # unmount the temporary directory created for copying the template umount(templateDirOnPrimary) # get the dummyVHD which is the parent of the new Vhd dummyUUID = getParent(newVhd, isISCSI) dummyVHD = getVHD(dummyUUID, isISCSI) # set the parent of the newVhd to the templateVHD on secondary setParent(templateVHD, newVhd) # remove the dummyVHD as we don't have any use for it and it wil # lie around after we do an SR scan os.remove(dummyVHD) # coalesce the two VHDs into templateVHD coalesce(newVhd) # rename templateVHD to newVhd rename(templateVHD, newVhd) return def rmtree(path): if os.path.isdir(path): try: shutil.rmtree(path) except OSError, (errno, strerror): errMsg = "Error while deleting " + path + " on secondary storage with errno: " + str(errno) + " and strerr: " + strerror + ". Please delete it manually" util.SMlog(errMsg) util.SMlog("Successfully deleted " + path) else: util.SMlog("Could not find directory with path " + path) return @echo def deleteSnapshotsDir(session, args): util.SMlog("Calling deleteSnapshotsDir with " + str(args)) dcId = args['dcId'] accountId = args['accountId'] volumeId = args['volumeId'] secondaryStorageMountPath = args['secondaryStorageMountPath'] backupsDir = mountSnapshotsDir(secondaryStorageMountPath, "snapshots", dcId, accountId, volumeId) accountDir = os.path.dirname(backupsDir) util.SMlog("accountDir is " + accountDir) rmtree(accountDir) return "1" if __name__ == "__main__": XenAPIPlugin.dispatch({"create_secondary_storage_folder":create_secondary_storage_folder, "delete_secondary_storage_folder":delete_secondary_storage_folder, "post_create_private_template":post_create_private_template, "backupSnapshot": backupSnapshot, "deleteSnapshotBackup": deleteSnapshotBackup, "createVolumeFromSnapshot": createVolumeFromSnapshot, "unmountSnapshotsDir": unmountSnapshotsDir, "deleteSnapshotsDir": deleteSnapshotsDir, "validateSnapshot" : validateSnapshot})