mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
1001 lines
38 KiB
Python
Executable File
1001 lines
38 KiB
Python
Executable File
#!/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 <secondaryStorageMountPath>/<templatePath> on
|
|
# SR.MOUNT_BASE/<random_uuid>
|
|
# 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/<dcId>/<relativeDir>
|
|
# And create <accountId>/<instanceId> dir on it, if it doesn't exist already.
|
|
# Assuming that secondaryStorageMountPath exists remotely
|
|
|
|
# Alex's suggestion and currently implemented:
|
|
# Just mount secondaryStorageMountPath/<relativeDir> 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/<dcId>/snapshots/ and create <accountId>/<volumeId> 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})
|
|
|