|
| 1 | +#!/usr/bin/env python3 |
| 2 | +# Licensed to the Apache Software Foundation (ASF) under one |
| 3 | +# or more contributor license agreements. See the NOTICE file |
| 4 | +# distributed with this work for additional information |
| 5 | +# regarding copyright ownership. The ASF licenses this file |
| 6 | +# to you under the Apache License, Version 2.0 (the |
| 7 | +# "License"); you may not use this file except in compliance |
| 8 | +# with the License. You may obtain a copy of the License at |
| 9 | +# |
| 10 | +# http://www.apache.org/licenses/LICENSE-2.0 |
| 11 | +# |
| 12 | +# Unless required by applicable law or agreed to in writing, |
| 13 | +# software distributed under the License is distributed on an |
| 14 | +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| 15 | +# KIND, either express or implied. See the License for the |
| 16 | +# specific language governing permissions and limitations |
| 17 | +# under the License. |
| 18 | +# FileSR: local-file storage repository |
| 19 | + |
| 20 | +import SR, VDI, SRCommand, FileSR, util |
| 21 | +import errno |
| 22 | +import os, re, sys, stat |
| 23 | +import time |
| 24 | +import xml.dom.minidom |
| 25 | +import xs_errors |
| 26 | +import nfs |
| 27 | +import vhdutil |
| 28 | +from lock import Lock |
| 29 | +import cleanup |
| 30 | + |
| 31 | +CAPABILITIES = ["SR_PROBE","SR_UPDATE", "SR_CACHING", \ |
| 32 | + "VDI_CREATE","VDI_DELETE","VDI_ATTACH","VDI_DETACH", \ |
| 33 | + "VDI_UPDATE", "VDI_CLONE","VDI_SNAPSHOT","VDI_RESIZE", \ |
| 34 | + "VDI_RESIZE_ONLINE", "VDI_RESET_ON_BOOT", "ATOMIC_PAUSE"] |
| 35 | + |
| 36 | +CONFIGURATION = [ [ 'server', 'hostname or IP address of NFS server (required)' ], \ |
| 37 | + [ 'serverpath', 'path on remote server (required)' ] ] |
| 38 | + |
| 39 | + |
| 40 | +DRIVER_INFO = { |
| 41 | + 'name': 'NFS VHD', |
| 42 | + 'description': 'SR plugin which stores disks as VHD files on a remote NFS filesystem', |
| 43 | + 'vendor': 'The Apache Software Foundation', |
| 44 | + 'copyright': 'Copyright (c) 2012 The Apache Software Foundation', |
| 45 | + 'driver_version': '1.0', |
| 46 | + 'required_api_version': '1.0', |
| 47 | + 'capabilities': CAPABILITIES, |
| 48 | + 'configuration': CONFIGURATION |
| 49 | + } |
| 50 | + |
| 51 | + |
| 52 | +# The mountpoint for the directory when performing an sr_probe. All probes |
| 53 | +PROBE_MOUNTPOINT = "probe" |
| 54 | +NFSPORT = 2049 |
| 55 | +DEFAULT_TRANSPORT = "tcp" |
| 56 | + |
| 57 | + |
| 58 | +class NFSSR(FileSR.FileSR): |
| 59 | + """NFS file-based storage repository""" |
| 60 | + def handles(type): |
| 61 | + return type == 'nfs' |
| 62 | + handles = staticmethod(handles) |
| 63 | + |
| 64 | + |
| 65 | + def load(self, sr_uuid): |
| 66 | + self.ops_exclusive = FileSR.OPS_EXCLUSIVE |
| 67 | + self.lock = Lock(vhdutil.LOCK_TYPE_SR, self.uuid) |
| 68 | + self.sr_vditype = SR.DEFAULT_TAP |
| 69 | + if not self.dconf.has_key('server'): |
| 70 | + raise xs_errors.XenError('ConfigServerMissing') |
| 71 | + self.remoteserver = self.dconf['server'] |
| 72 | + self.path = os.path.join(SR.MOUNT_BASE, sr_uuid) |
| 73 | + |
| 74 | + # Test for the optional 'nfsoptions' dconf attribute |
| 75 | + self.transport = DEFAULT_TRANSPORT |
| 76 | + if self.dconf.has_key('useUDP') and self.dconf['useUDP'] == 'true': |
| 77 | + self.transport = "udp" |
| 78 | + |
| 79 | + |
| 80 | + def validate_remotepath(self, scan): |
| 81 | + if not self.dconf.has_key('serverpath'): |
| 82 | + if scan: |
| 83 | + try: |
| 84 | + self.scan_exports(self.dconf['server']) |
| 85 | + except: |
| 86 | + pass |
| 87 | + raise xs_errors.XenError('ConfigServerPathMissing') |
| 88 | + if not self._isvalidpathstring(self.dconf['serverpath']): |
| 89 | + raise xs_errors.XenError('ConfigServerPathBad', \ |
| 90 | + opterr='serverpath is %s' % self.dconf['serverpath']) |
| 91 | + |
| 92 | + def check_server(self): |
| 93 | + try: |
| 94 | + nfs.check_server_tcp(self.remoteserver) |
| 95 | + except nfs.NfsException as exc: |
| 96 | + raise xs_errors.XenError('NFSVersion', |
| 97 | + opterr=exc.errstr) |
| 98 | + |
| 99 | + |
| 100 | + def mount(self, mountpoint, remotepath): |
| 101 | + try: |
| 102 | + nfs.soft_mount(mountpoint, self.remoteserver, remotepath, self.transport) |
| 103 | + except nfs.NfsException as exc: |
| 104 | + raise xs_errors.XenError('NFSMount', opterr=exc.errstr) |
| 105 | + |
| 106 | + |
| 107 | + def attach(self, sr_uuid): |
| 108 | + self.validate_remotepath(False) |
| 109 | + #self.remotepath = os.path.join(self.dconf['serverpath'], sr_uuid) |
| 110 | + self.remotepath = self.dconf['serverpath'] |
| 111 | + util._testHost(self.dconf['server'], NFSPORT, 'NFSTarget') |
| 112 | + self.mount_remotepath(sr_uuid) |
| 113 | + |
| 114 | + |
| 115 | + def mount_remotepath(self, sr_uuid): |
| 116 | + if not self._checkmount(): |
| 117 | + self.check_server() |
| 118 | + self.mount(self.path, self.remotepath) |
| 119 | + |
| 120 | + return super(NFSSR, self).attach(sr_uuid) |
| 121 | + |
| 122 | + |
| 123 | + def probe(self): |
| 124 | + # Verify NFS target and port |
| 125 | + util._testHost(self.dconf['server'], NFSPORT, 'NFSTarget') |
| 126 | + |
| 127 | + self.validate_remotepath(True) |
| 128 | + self.check_server() |
| 129 | + |
| 130 | + temppath = os.path.join(SR.MOUNT_BASE, PROBE_MOUNTPOINT) |
| 131 | + |
| 132 | + self.mount(temppath, self.dconf['serverpath']) |
| 133 | + try: |
| 134 | + return nfs.scan_srlist(temppath) |
| 135 | + finally: |
| 136 | + try: |
| 137 | + nfs.unmount(temppath, True) |
| 138 | + except: |
| 139 | + pass |
| 140 | + |
| 141 | + |
| 142 | + def detach(self, sr_uuid): |
| 143 | + """Detach the SR: Unmounts and removes the mountpoint""" |
| 144 | + if not self._checkmount(): |
| 145 | + return |
| 146 | + util.SMlog("Aborting GC/coalesce") |
| 147 | + cleanup.abort(self.uuid) |
| 148 | + |
| 149 | + # Change directory to avoid unmount conflicts |
| 150 | + os.chdir(SR.MOUNT_BASE) |
| 151 | + |
| 152 | + try: |
| 153 | + nfs.unmount(self.path, True) |
| 154 | + except nfs.NfsException as exc: |
| 155 | + raise xs_errors.XenError('NFSUnMount', opterr=exc.errstr) |
| 156 | + |
| 157 | + return super(NFSSR, self).detach(sr_uuid) |
| 158 | + |
| 159 | + |
| 160 | + def create(self, sr_uuid, size): |
| 161 | + util._testHost(self.dconf['server'], NFSPORT, 'NFSTarget') |
| 162 | + self.validate_remotepath(True) |
| 163 | + if self._checkmount(): |
| 164 | + raise xs_errors.XenError('NFSAttached') |
| 165 | + |
| 166 | + # Set the target path temporarily to the base dir |
| 167 | + # so that we can create the target SR directory |
| 168 | + self.remotepath = self.dconf['serverpath'] |
| 169 | + try: |
| 170 | + self.mount_remotepath(sr_uuid) |
| 171 | + except Exception as exn: |
| 172 | + try: |
| 173 | + os.rmdir(self.path) |
| 174 | + except: |
| 175 | + pass |
| 176 | + raise exn |
| 177 | + |
| 178 | + #newpath = os.path.join(self.path, sr_uuid) |
| 179 | + #if util.ioretry(lambda: util.pathexists(newpath)): |
| 180 | + # if len(util.ioretry(lambda: util.listdir(newpath))) != 0: |
| 181 | + # self.detach(sr_uuid) |
| 182 | + # raise xs_errors.XenError('SRExists') |
| 183 | + #else: |
| 184 | + # try: |
| 185 | + # util.ioretry(lambda: util.makedirs(newpath)) |
| 186 | + # except util.CommandException, inst: |
| 187 | + # if inst.code != errno.EEXIST: |
| 188 | + # self.detach(sr_uuid) |
| 189 | + # raise xs_errors.XenError('NFSCreate', |
| 190 | + # opterr='remote directory creation error is %d' |
| 191 | + # % inst.code) |
| 192 | + self.detach(sr_uuid) |
| 193 | + |
| 194 | + def delete(self, sr_uuid): |
| 195 | + # try to remove/delete non VDI contents first |
| 196 | + super(NFSSR, self).delete(sr_uuid) |
| 197 | + try: |
| 198 | + if self._checkmount(): |
| 199 | + self.detach(sr_uuid) |
| 200 | + |
| 201 | + # Set the target path temporarily to the base dir |
| 202 | + # so that we can remove the target SR directory |
| 203 | + self.remotepath = self.dconf['serverpath'] |
| 204 | + self.mount_remotepath(sr_uuid) |
| 205 | + newpath = os.path.join(self.path, sr_uuid) |
| 206 | + |
| 207 | + if util.ioretry(lambda: util.pathexists(newpath)): |
| 208 | + util.ioretry(lambda: os.rmdir(newpath)) |
| 209 | + self.detach(sr_uuid) |
| 210 | + except util.CommandException as inst: |
| 211 | + self.detach(sr_uuid) |
| 212 | + if inst.code != errno.ENOENT: |
| 213 | + raise xs_errors.XenError('NFSDelete') |
| 214 | + |
| 215 | + def vdi(self, uuid, loadLocked = False): |
| 216 | + if not loadLocked: |
| 217 | + return NFSFileVDI(self, uuid) |
| 218 | + return NFSFileVDI(self, uuid) |
| 219 | + |
| 220 | + def _checkmount(self): |
| 221 | + return util.ioretry(lambda: util.pathexists(self.path)) \ |
| 222 | + and util.ioretry(lambda: util.ismount(self.path)) |
| 223 | + |
| 224 | + def scan_exports(self, target): |
| 225 | + util.SMlog("scanning2 (target=%s)" % target) |
| 226 | + dom = nfs.scan_exports(target) |
| 227 | + print >>sys.stderr,dom.toprettyxml() |
| 228 | + |
| 229 | +class NFSFileVDI(FileSR.FileVDI): |
| 230 | + def attach(self, sr_uuid, vdi_uuid): |
| 231 | + try: |
| 232 | + vdi_ref = self.sr.srcmd.params['vdi_ref'] |
| 233 | + self.session.xenapi.VDI.remove_from_xenstore_data(vdi_ref, \ |
| 234 | + "vdi-type") |
| 235 | + self.session.xenapi.VDI.remove_from_xenstore_data(vdi_ref, \ |
| 236 | + "storage-type") |
| 237 | + self.session.xenapi.VDI.add_to_xenstore_data(vdi_ref, \ |
| 238 | + "storage-type", "nfs") |
| 239 | + except: |
| 240 | + util.logException("NFSSR:attach") |
| 241 | + pass |
| 242 | + return super(NFSFileVDI, self).attach(sr_uuid, vdi_uuid) |
| 243 | + |
| 244 | + def get_mtime(self, path): |
| 245 | + st = util.ioretry_stat(lambda: os.stat(path)) |
| 246 | + return st[stat.ST_MTIME] |
| 247 | + |
| 248 | + def clone(self, sr_uuid, vdi_uuid): |
| 249 | + timestamp_before = int(self.get_mtime(self.sr.path)) |
| 250 | + ret = super(NFSFileVDI, self).clone(sr_uuid, vdi_uuid) |
| 251 | + timestamp_after = int(self.get_mtime(self.sr.path)) |
| 252 | + if timestamp_after == timestamp_before: |
| 253 | + util.SMlog("SR dir timestamp didn't change, updating") |
| 254 | + timestamp_after += 1 |
| 255 | + os.utime(self.sr.path, (timestamp_after, timestamp_after)) |
| 256 | + return ret |
| 257 | + |
| 258 | + |
| 259 | +if __name__ == '__main__': |
| 260 | + SRCommand.run(NFSSR, DRIVER_INFO) |
| 261 | +else: |
| 262 | + SR.registerSR(NFSSR) |
0 commit comments