| #!/usr/bin/python |
| # Licensed to the Apache Software Foundation (ASF) under one |
| # or more contributor license agreements. See the NOTICE file |
| # distributed with this work for additional information |
| # regarding copyright ownership. The ASF licenses this file |
| # to you under the Apache License, Version 2.0 (the |
| # "License"); you may not use this file except in compliance |
| # with the License. You may obtain a copy of the License at |
| # |
| # http://www.apache.org/licenses/LICENSE-2.0 |
| # |
| # Unless required by applicable law or agreed to in writing, |
| # software distributed under the License is distributed on an |
| # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| # KIND, either express or implied. See the License for the |
| # specific language governing permissions and limitations |
| # under the License. |
| |
| # FileSR: local-file storage repository |
| import SR, VDI, SRCommand, FileSR, util |
| import errno |
| import os, re, sys, stat |
| 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': 'The Apache Software Foundation', |
| 'copyright': 'Copyright (c) 2012 The Apache Software Foundation', |
| '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 |
| 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 get_mtime(self, path): |
| st = util.ioretry_stat(lambda: os.stat(path)) |
| return st[stat.ST_MTIME] |
| |
| def clone(self, sr_uuid, vdi_uuid): |
| timestamp_before = int(self.get_mtime(self.sr.path)) |
| ret = super(NFSFileVDI, self).clone(sr_uuid, vdi_uuid) |
| timestamp_after = int(self.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) |