diff --git a/scripts/vm/hypervisor/xenserver/xenserver56fp1/NFSSR.py b/scripts/vm/hypervisor/xenserver/xenserver56fp1/NFSSR.py new file mode 100755 index 00000000000..81b1a38dd76 --- /dev/null +++ b/scripts/vm/hypervisor/xenserver/xenserver56fp1/NFSSR.py @@ -0,0 +1,256 @@ +#!/usr/bin/python +# Copyright (C) 2006-2007 XenSource Ltd. +# Copyright (C) 2008-2009 Citrix Ltd. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation; version 2.1 only. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# FileSR: local-file storage repository + +import SR, VDI, SRCommand, FileSR, util +import errno +import os, re, sys +import time +import xml.dom.minidom +import xs_errors +import nfs +import vhdutil +from lock import Lock +import cleanup + +CAPABILITIES = ["SR_PROBE","SR_UPDATE", "SR_CACHING", \ + "VDI_CREATE","VDI_DELETE","VDI_ATTACH","VDI_DETACH", \ + "VDI_UPDATE", "VDI_CLONE","VDI_SNAPSHOT","VDI_RESIZE", \ + "VDI_RESIZE_ONLINE", "VDI_RESET_ON_BOOT", "ATOMIC_PAUSE"] + +CONFIGURATION = [ [ 'server', 'hostname or IP address of NFS server (required)' ], \ + [ 'serverpath', 'path on remote server (required)' ] ] + + +DRIVER_INFO = { + 'name': 'NFS VHD', + 'description': 'SR plugin which stores disks as VHD files on a remote NFS filesystem', + 'vendor': 'Citrix Systems Inc', + 'copyright': '(C) 2008 Citrix Systems Inc', + 'driver_version': '1.0', + 'required_api_version': '1.0', + 'capabilities': CAPABILITIES, + 'configuration': CONFIGURATION + } + + +# The mountpoint for the directory when performing an sr_probe. All probes +# are guaranteed to be serialised by xapi, so this single mountpoint is fine. +PROBE_MOUNTPOINT = "probe" +NFSPORT = 2049 +DEFAULT_TRANSPORT = "tcp" + + +class NFSSR(FileSR.FileSR): + """NFS file-based storage repository""" + def handles(type): + return type == 'nfs' + handles = staticmethod(handles) + + + def load(self, sr_uuid): + self.ops_exclusive = FileSR.OPS_EXCLUSIVE + self.lock = Lock(vhdutil.LOCK_TYPE_SR, self.uuid) + self.sr_vditype = SR.DEFAULT_TAP + if not self.dconf.has_key('server'): + raise xs_errors.XenError('ConfigServerMissing') + self.remoteserver = self.dconf['server'] + self.path = os.path.join(SR.MOUNT_BASE, sr_uuid) + + # Test for the optional 'nfsoptions' dconf attribute + self.transport = DEFAULT_TRANSPORT + if self.dconf.has_key('useUDP') and self.dconf['useUDP'] == 'true': + self.transport = "udp" + + + def validate_remotepath(self, scan): + if not self.dconf.has_key('serverpath'): + if scan: + try: + self.scan_exports(self.dconf['server']) + except: + pass + raise xs_errors.XenError('ConfigServerPathMissing') + if not self._isvalidpathstring(self.dconf['serverpath']): + raise xs_errors.XenError('ConfigServerPathBad', \ + opterr='serverpath is %s' % self.dconf['serverpath']) + + def check_server(self): + try: + nfs.check_server_tcp(self.remoteserver) + except nfs.NfsException, exc: + raise xs_errors.XenError('NFSVersion', + opterr=exc.errstr) + + + def mount(self, mountpoint, remotepath): + try: + nfs.soft_mount(mountpoint, self.remoteserver, remotepath, self.transport) + except nfs.NfsException, exc: + raise xs_errors.XenError('NFSMount', opterr=exc.errstr) + + + def attach(self, sr_uuid): + self.validate_remotepath(False) + #self.remotepath = os.path.join(self.dconf['serverpath'], sr_uuid) + self.remotepath = self.dconf['serverpath'] + util._testHost(self.dconf['server'], NFSPORT, 'NFSTarget') + self.mount_remotepath(sr_uuid) + + + def mount_remotepath(self, sr_uuid): + if not self._checkmount(): + self.check_server() + self.mount(self.path, self.remotepath) + + return super(NFSSR, self).attach(sr_uuid) + + + def probe(self): + # Verify NFS target and port + util._testHost(self.dconf['server'], NFSPORT, 'NFSTarget') + + self.validate_remotepath(True) + self.check_server() + + temppath = os.path.join(SR.MOUNT_BASE, PROBE_MOUNTPOINT) + + self.mount(temppath, self.dconf['serverpath']) + try: + return nfs.scan_srlist(temppath) + finally: + try: + nfs.unmount(temppath, True) + except: + pass + + + def detach(self, sr_uuid): + """Detach the SR: Unmounts and removes the mountpoint""" + if not self._checkmount(): + return + util.SMlog("Aborting GC/coalesce") + cleanup.abort(self.uuid) + + # Change directory to avoid unmount conflicts + os.chdir(SR.MOUNT_BASE) + + try: + nfs.unmount(self.path, True) + except nfs.NfsException, exc: + raise xs_errors.XenError('NFSUnMount', opterr=exc.errstr) + + return super(NFSSR, self).detach(sr_uuid) + + + def create(self, sr_uuid, size): + util._testHost(self.dconf['server'], NFSPORT, 'NFSTarget') + self.validate_remotepath(True) + if self._checkmount(): + raise xs_errors.XenError('NFSAttached') + + # Set the target path temporarily to the base dir + # so that we can create the target SR directory + self.remotepath = self.dconf['serverpath'] + try: + self.mount_remotepath(sr_uuid) + except Exception, exn: + try: + os.rmdir(self.path) + except: + pass + raise exn + + #newpath = os.path.join(self.path, sr_uuid) + #if util.ioretry(lambda: util.pathexists(newpath)): + # if len(util.ioretry(lambda: util.listdir(newpath))) != 0: + # self.detach(sr_uuid) + # raise xs_errors.XenError('SRExists') + #else: + # try: + # util.ioretry(lambda: util.makedirs(newpath)) + # except util.CommandException, inst: + # if inst.code != errno.EEXIST: + # self.detach(sr_uuid) + # raise xs_errors.XenError('NFSCreate', + # opterr='remote directory creation error is %d' + # % inst.code) + self.detach(sr_uuid) + + def delete(self, sr_uuid): + # try to remove/delete non VDI contents first + super(NFSSR, self).delete(sr_uuid) + try: + if self._checkmount(): + self.detach(sr_uuid) + + # Set the target path temporarily to the base dir + # so that we can remove the target SR directory + self.remotepath = self.dconf['serverpath'] + self.mount_remotepath(sr_uuid) + newpath = os.path.join(self.path, sr_uuid) + + if util.ioretry(lambda: util.pathexists(newpath)): + util.ioretry(lambda: os.rmdir(newpath)) + self.detach(sr_uuid) + except util.CommandException, inst: + self.detach(sr_uuid) + if inst.code != errno.ENOENT: + raise xs_errors.XenError('NFSDelete') + + def vdi(self, uuid, loadLocked = False): + if not loadLocked: + return NFSFileVDI(self, uuid) + return NFSFileVDI(self, uuid) + + def _checkmount(self): + return util.ioretry(lambda: util.pathexists(self.path)) \ + and util.ioretry(lambda: util.ismount(self.path)) + + def scan_exports(self, target): + util.SMlog("scanning2 (target=%s)" % target) + dom = nfs.scan_exports(target) + print >>sys.stderr,dom.toprettyxml() + +class NFSFileVDI(FileSR.FileVDI): + def attach(self, sr_uuid, vdi_uuid): + try: + vdi_ref = self.sr.srcmd.params['vdi_ref'] + self.session.xenapi.VDI.remove_from_xenstore_data(vdi_ref, \ + "vdi-type") + self.session.xenapi.VDI.remove_from_xenstore_data(vdi_ref, \ + "storage-type") + self.session.xenapi.VDI.add_to_xenstore_data(vdi_ref, \ + "storage-type", "nfs") + except: + util.logException("NFSSR:attach") + pass + + return super(NFSFileVDI, self).attach(sr_uuid, vdi_uuid) + + def clone(self, sr_uuid, vdi_uuid): + timestamp_before = int(util.get_mtime(self.sr.path)) + ret = super(NFSFileVDI, self).clone(sr_uuid, vdi_uuid) + timestamp_after = int(util.get_mtime(self.sr.path)) + if timestamp_after == timestamp_before: + util.SMlog("SR dir timestamp didn't change, updating") + timestamp_after += 1 + os.utime(self.sr.path, (timestamp_after, timestamp_after)) + return ret + + +if __name__ == '__main__': + SRCommand.run(NFSSR, DRIVER_INFO) +else: + SR.registerSR(NFSSR) diff --git a/scripts/vm/hypervisor/xenserver/xenserver56fp1/nfs.py b/scripts/vm/hypervisor/xenserver/xenserver56fp1/nfs.py new file mode 100755 index 00000000000..3a8463e30c5 --- /dev/null +++ b/scripts/vm/hypervisor/xenserver/xenserver56fp1/nfs.py @@ -0,0 +1,145 @@ +#!/usr/bin/python +# Copyright (C) 2006-2007 XenSource Ltd. +# Copyright (C) 2008-2009 Citrix Ltd. +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU Lesser General Public License as published +# by the Free Software Foundation; version 2.1 only. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU Lesser General Public License for more details. +# +# nfs.py: NFS related utility functions + +import util, errno, os, xml.dom.minidom + +# The algorithm for tcp and udp (at least in the linux kernel) for +# NFS timeout on softmounts is as follows: +# +# UDP: +# As long as the request wasn't started more than timeo * (2 ^ retrans) +# in the past, keep doubling the timeout. +# +# TCP: +# As long as the request wasn't started more than timeo * (1 + retrans) +# in the past, keep increaing the timeout by timeo. +# +# The time when the retrans may retry has been made will be: +# For udp: timeo * (2 ^ retrans * 2 - 1) +# For tcp: timeo * n! where n is the smallest n for which n! > 1 + retrans +# +# thus for retrans=1, timeo can be the same for both tcp and udp, +# because the first doubling (timeo*2) is the same as the first increment +# (timeo+timeo). + +SOFTMOUNT_TIMEOUT = int((40.0/3.0) * 10.0) # 1/10 s +SOFTMOUNT_RETRANS = 10 +RPCINFO_BIN = "/usr/sbin/rpcinfo" +SHOWMOUNT_BIN = "/usr/sbin/showmount" + + +class NfsException(Exception): + def __init__(self, errstr): + self.errstr = errstr + + +def check_server_tcp(server): + """Make sure that NFS over TCP/IP V3 is supported on the server. + Returns True if everything is OK, False otherwise.""" + try: + util.ioretry(lambda: util.pread([RPCINFO_BIN,"-t", + "%s" % server, "nfs","3"]), + errlist=[errno.EPERM], maxretry=2, nofail=True) + except util.CommandException, inst: + raise NfsException("rpcinfo failed or timed out: return code %d" % + inst.code) + + +def soft_mount(mountpoint, remoteserver, remotepath, transport): + """Mount the remote NFS export at 'mountpoint'""" + try: + if not util.ioretry(lambda: util.isdir(mountpoint)): + util.ioretry(lambda: util.makedirs(mountpoint)) + except util.CommandException, inst: + raise NfsException("Failed to make directory: code is %d" % + inst.code) + + options = "soft,timeo=%d,retrans=%d,%s,noac" % (SOFTMOUNT_TIMEOUT, + SOFTMOUNT_RETRANS, + transport) + try: + util.ioretry(lambda: + util.pread(["mount.nfs", "%s:%s" + % (remoteserver, remotepath), + mountpoint, "-o", options]), + errlist=[errno.EPIPE, errno.EIO], + maxretry=2, nofail=True) + except util.CommandException, inst: + raise NfsException("mount failed with return code %d" % inst.code) + + +def unmount(mountpoint, rmmountpoint): + """Unmount the mounted mountpoint""" + try: + util.pread(["umount", mountpoint]) + except util.CommandException, inst: + raise NfsException("umount failed with return code %d" % inst.code) + + if rmmountpoint: + try: + os.rmdir(mountpoint) + except OSError, inst: + raise NfsException("rmdir failed with error '%s'" % inst.strerror) + + +def scan_exports(target): + util.SMlog("scanning") + cmd = [SHOWMOUNT_BIN, "--no-headers", "-e", target] + dom = xml.dom.minidom.Document() + element = dom.createElement("nfs-exports") + dom.appendChild(element) + for val in util.pread2(cmd).split('\n'): + if not len(val): + continue + entry = dom.createElement('Export') + element.appendChild(entry) + + subentry = dom.createElement("Target") + entry.appendChild(subentry) + textnode = dom.createTextNode(target) + subentry.appendChild(textnode) + + (path, access) = val.split() + subentry = dom.createElement("Path") + entry.appendChild(subentry) + textnode = dom.createTextNode(path) + subentry.appendChild(textnode) + + subentry = dom.createElement("Accesslist") + entry.appendChild(subentry) + textnode = dom.createTextNode(access) + subentry.appendChild(textnode) + + return dom + +def scan_srlist(path): + dom = xml.dom.minidom.Document() + element = dom.createElement("SRlist") + dom.appendChild(element) + for val in filter(util.match_uuid, util.ioretry( \ + lambda: util.listdir(path))): + fullpath = os.path.join(path, val) + if not util.ioretry(lambda: util.isdir(fullpath)): + continue + + entry = dom.createElement('SR') + element.appendChild(entry) + + subentry = dom.createElement("UUID") + entry.appendChild(subentry) + textnode = dom.createTextNode(val) + subentry.appendChild(textnode) + + return dom.toprettyxml() diff --git a/scripts/vm/hypervisor/xenserver/xenserver56fp1/patch b/scripts/vm/hypervisor/xenserver/xenserver56fp1/patch new file mode 100644 index 00000000000..69adddefc78 --- /dev/null +++ b/scripts/vm/hypervisor/xenserver/xenserver56fp1/patch @@ -0,0 +1,31 @@ +# This file specifies the files that need +# to be transferred over to the XenServer. +# The format of this file is as follows: +# [Name of file]=[source path],[file permission],[destination path] +# [destination path] is required. +# If [file permission] is missing, 755 is assumed. +# If [source path] is missing, it looks in the same +# directory as the patch file. +# If [source path] starts with '/', then it is absolute path. +# If [source path] starts with '~', then it is path relative to management server home directory. +# If [source path] does not start with '/' or '~', then it is relative path to the location of the patch file. +NFSSR.py=/opt/xensource/sm +nfs.py=/opt/xensource/sm +patch.tgz=..,0775,/opt/xensource/bin +vmops=..,0755,/etc/xapi.d/plugins +systemvm.zip=../../../../../vms,0755,/opt/xensource/bin +hostvmstats.py=..,0755,/opt/xensource/sm +network_info.sh=..,0755,/opt/xensource/bin +prepsystemvm.sh=..,0755,/opt/xensource/bin +setupxenserver.sh=..,0755,/opt/xensource/bin +make_migratable.sh=..,0755,/opt/xensource/bin +networkUsage.sh=..,0755,/opt/xensource/bin +setup_iscsi.sh=..,0755,/opt/xensource/bin +version=..,0755,/opt/xensource/bin +pingtest.sh=../../..,0755,/opt/xensource/bin +dhcp_entry.sh=../../../../network/domr/,0755,/opt/xensource/bin +ipassoc.sh=../../../../network/domr/,0755,/opt/xensource/bin +vm_data.sh=../../../../network/domr/,0755,/opt/xensource/bin +save_password_to_domr.sh=../../../../network/domr/,0755,/opt/xensource/bin +call_firewall.sh=../../../../network/domr/,0755,/opt/xensource/bin +call_loadbalancer.sh=../../../../network/domr/,0755,/opt/xensource/bin