mirror of
https://github.com/apache/cloudstack.git
synced 2025-10-26 08:42:29 +01:00
553 lines
20 KiB
Python
553 lines
20 KiB
Python
#!/usr/bin/python
|
|
# Version @VERSION@
|
|
#
|
|
# A plugin for executing script needed by vmops cloud
|
|
|
|
import os, sys, time
|
|
import XenAPIPlugin
|
|
sys.path.append("/usr/lib/xcp/sm/")
|
|
import SR, VDI, SRCommand, util, lvutil
|
|
from util import CommandException
|
|
import vhdutil
|
|
import shutil
|
|
import lvhdutil
|
|
import errno
|
|
import subprocess
|
|
import xs_errors
|
|
import cleanup
|
|
import stat
|
|
import random
|
|
|
|
VHD_UTIL = 'vhd-util'
|
|
VHD_PREFIX = 'VHD-'
|
|
CLOUD_DIR = '/run/cloud_mount'
|
|
|
|
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(CLOUD_DIR, util.gen_uuid())
|
|
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("rmdir " + 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(CLOUD_DIR, util.gen_uuid())
|
|
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 -f " + folder + "/*")
|
|
os.system("rmdir " + 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("rmdir " + local_mount_path)
|
|
|
|
return "1"
|
|
|
|
@echo
|
|
def post_create_private_template(session, args):
|
|
local_mount_path = None
|
|
try:
|
|
try:
|
|
# get local template folder
|
|
templatePath = args["templatePath"]
|
|
local_mount_path = os.path.join(CLOUD_DIR, util.gen_uuid())
|
|
mount(templatePath, local_mount_path)
|
|
# Retrieve args
|
|
filename = args["templateFilename"]
|
|
name = args["templateName"]
|
|
description = args["templateDescription"]
|
|
checksum = args["checksum"]
|
|
file_size = args["size"]
|
|
virtual_size = args["virtualSize"]
|
|
template_id = args["templateId"]
|
|
|
|
# Create the template.properties file
|
|
template_properties_install_path = local_mount_path + "/template.properties"
|
|
f = open(template_properties_install_path, "w")
|
|
f.write("filename=" + filename + "\n")
|
|
f.write("vhd=true\n")
|
|
f.write("id=" + template_id + "\n")
|
|
f.write("vhd.filename=" + filename + "\n")
|
|
f.write("public=false\n")
|
|
f.write("uniquename=" + name + "\n")
|
|
f.write("vhd.virtualsize=" + virtual_size + "\n")
|
|
f.write("virtualsize=" + virtual_size + "\n")
|
|
f.write("checksum=" + checksum + "\n")
|
|
f.write("hvm=true\n")
|
|
f.write("description=" + description + "\n")
|
|
f.write("vhd.size=" + str(file_size) + "\n")
|
|
f.write("size=" + str(file_size) + "\n")
|
|
f.close()
|
|
util.SMlog("Created template.properties file")
|
|
|
|
# Set permissions
|
|
permissions = stat.S_IREAD | stat.S_IWRITE | stat.S_IRGRP | stat.S_IWGRP | stat.S_IROTH | stat.S_IWOTH
|
|
os.chmod(template_properties_install_path, permissions)
|
|
util.SMlog("Set permissions on template and template.properties")
|
|
|
|
except:
|
|
errMsg = "post_create_private_template 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("rmdir " + 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 = ''
|
|
try:
|
|
cmd = ['dd', 'if=' + fromFile, 'of=' + toFile, 'bs=4M']
|
|
txt = util.pread2(cmd)
|
|
except:
|
|
try:
|
|
os.system("rm -f " + toFile)
|
|
except:
|
|
txt = ''
|
|
txt = ''
|
|
errMsg = "Error while copying " + fromFile + " to " + toFile + " in secondary storage"
|
|
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 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 makedirs(path):
|
|
if not os.path.isdir(path):
|
|
try:
|
|
os.makedirs(path)
|
|
except OSError, (errno, strerror):
|
|
umount(path)
|
|
if os.path.isdir(path):
|
|
return
|
|
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)
|
|
options = "soft,tcp,timeo=133,retrans=1"
|
|
try:
|
|
cmd = ['mount', '-o', options, 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):
|
|
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)
|
|
return
|
|
|
|
def mountSnapshotsDir(secondaryStorageMountPath, relativeDir, dcId, accountId, instanceId):
|
|
# The aim is to mount secondaryStorageMountPath on
|
|
# 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(CLOUD_DIR, 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(CLOUD_DIR, 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(session, primaryStorageSRUuid, isISCSI):
|
|
sr = session.xenapi.SR.get_by_uuid(primaryStorageSRUuid)
|
|
srrec = session.xenapi.SR.get_record(sr)
|
|
srtype = srrec["type"]
|
|
if srtype == "file":
|
|
pbd = session.xenapi.SR.get_PBDs(sr)[0]
|
|
pbdrec = session.xenapi.PBD.get_record(pbd)
|
|
primarySRPath = pbdrec["device_config"]["location"]
|
|
return primarySRPath
|
|
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 getBackupVHD(UUID):
|
|
return UUID + '.' + SR.DEFAULT_TAP
|
|
|
|
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):
|
|
if not isISCSI:
|
|
return
|
|
VHD = getVHD(uuid, isISCSI)
|
|
path = os.path.join(primarySRPath, VHD)
|
|
manageAvailability(path, '-an')
|
|
return
|
|
|
|
def manageAvailability(path, value):
|
|
if path.__contains__("/var/run/sr-mount"):
|
|
return
|
|
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 getVhdParent(session, args):
|
|
util.SMlog("getParent with " + str(args))
|
|
primaryStorageSRUuid = args['primaryStorageSRUuid']
|
|
snapshotUuid = args['snapshotUuid']
|
|
isISCSI = getIsTrueString(args['isISCSI'])
|
|
|
|
primarySRPath = getPrimarySRPath(session, primaryStorageSRUuid, isISCSI)
|
|
util.SMlog("primarySRPath: " + primarySRPath)
|
|
|
|
baseCopyUuid = getParentOfSnapshot(snapshotUuid, primarySRPath, isISCSI)
|
|
|
|
return baseCopyUuid
|
|
|
|
|
|
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']
|
|
prevBackupUuid = args['prevBackupUuid']
|
|
backupUuid = args['backupUuid']
|
|
isISCSI = getIsTrueString(args['isISCSI'])
|
|
|
|
primarySRPath = getPrimarySRPath(session, 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)
|
|
|
|
|
|
# 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)
|
|
|
|
# Check existence of snapshot on primary storage
|
|
isfile(baseCopyPath, isISCSI)
|
|
if prevBackupUuid:
|
|
# Check existence of prevBackupFile
|
|
prevBackupVHD = getBackupVHD(prevBackupUuid)
|
|
prevBackupFile = os.path.join(backupsDir, prevBackupVHD)
|
|
isfile(prevBackupFile, False)
|
|
|
|
# copy baseCopyPath to backupsDir with new uuid
|
|
backupVHD = getBackupVHD(backupUuid)
|
|
backupFile = os.path.join(backupsDir, backupVHD)
|
|
util.SMlog("Back up " + baseCopyUuid + " to Secondary Storage as " + backupUuid)
|
|
copyfile(baseCopyPath, backupFile, isISCSI)
|
|
vhdutil.setHidden(backupFile, False)
|
|
|
|
# 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
|
|
setParent(prevBackupFile, backupFile)
|
|
|
|
txt = "1#" + backupUuid
|
|
return txt
|
|
|
|
@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']
|
|
|
|
backupsDir = mountSnapshotsDir(secondaryStorageMountPath, "snapshots", dcId, accountId, volumeId)
|
|
# chdir to the backupsDir for convenience
|
|
chdir(backupsDir)
|
|
|
|
backupVHD = getBackupVHD(backupUUID)
|
|
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.")
|
|
|
|
# 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"
|
|
|
|
if __name__ == "__main__":
|
|
XenAPIPlugin.dispatch({"getVhdParent":getVhdParent, "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, "unmountSnapshotsDir": unmountSnapshotsDir})
|
|
|