OpenStack Compute (Nova)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

api.py 8.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. # vim: tabstop=4 shiftwidth=4 softtabstop=4
  2. # Copyright 2011 Red Hat, Inc.
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License"); you may
  5. # not use this file except in compliance with the License. You may obtain
  6. # a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. # License for the specific language governing permissions and limitations
  14. # under the License.
  15. """Support for mounting virtual image files."""
  16. import os
  17. import time
  18. from nova.openstack.common import importutils
  19. from nova.openstack.common import log as logging
  20. from nova import utils
  21. LOG = logging.getLogger(__name__)
  22. MAX_DEVICE_WAIT = 30
  23. class Mount(object):
  24. """Standard mounting operations, that can be overridden by subclasses.
  25. The basic device operations provided are get, map and mount,
  26. to be called in that order.
  27. """
  28. mode = None # to be overridden in subclasses
  29. @staticmethod
  30. def instance_for_format(imgfile, mountdir, partition, imgfmt):
  31. LOG.debug(_("Instance for format imgfile=%(imgfile)s "
  32. "mountdir=%(mountdir)s partition=%(partition)s "
  33. "imgfmt=%(imgfmt)s") % locals())
  34. if imgfmt == "raw":
  35. LOG.debug(_("Using LoopMount"))
  36. return importutils.import_object(
  37. "nova.virt.disk.mount.loop.LoopMount",
  38. imgfile, mountdir, partition)
  39. else:
  40. LOG.debug(_("Using NbdMount"))
  41. return importutils.import_object(
  42. "nova.virt.disk.mount.nbd.NbdMount",
  43. imgfile, mountdir, partition)
  44. @staticmethod
  45. def instance_for_device(imgfile, mountdir, partition, device):
  46. LOG.debug(_("Instance for device imgfile=%(imgfile)s "
  47. "mountdir=%(mountdir)s partition=%(partition)s "
  48. "device=%(device)s") % locals())
  49. if "loop" in device:
  50. LOG.debug(_("Using LoopMount"))
  51. return importutils.import_object(
  52. "nova.virt.disk.mount.loop.LoopMount",
  53. imgfile, mountdir, partition, device)
  54. else:
  55. LOG.debug(_("Using NbdMount"))
  56. return importutils.import_object(
  57. "nova.virt.disk.mount.nbd.NbdMount",
  58. imgfile, mountdir, partition, device)
  59. def __init__(self, image, mount_dir, partition=None, device=None):
  60. # Input
  61. self.image = image
  62. self.partition = partition
  63. self.mount_dir = mount_dir
  64. # Output
  65. self.error = ""
  66. # Internal
  67. self.linked = self.mapped = self.mounted = self.automapped = False
  68. self.device = self.mapped_device = device
  69. # Reset to mounted dir if possible
  70. self.reset_dev()
  71. def reset_dev(self):
  72. """Reset device paths to allow unmounting."""
  73. if not self.device:
  74. return
  75. self.linked = self.mapped = self.mounted = True
  76. device = self.device
  77. if os.path.isabs(device) and os.path.exists(device):
  78. if device.startswith('/dev/mapper/'):
  79. device = os.path.basename(device)
  80. device, self.partition = device.rsplit('p', 1)
  81. self.device = os.path.join('/dev', device)
  82. def get_dev(self):
  83. """Make the image available as a block device in the file system."""
  84. self.device = None
  85. self.linked = True
  86. return True
  87. def _get_dev_retry_helper(self):
  88. """Some implementations need to retry their get_dev."""
  89. # NOTE(mikal): This method helps implement retries. The implementation
  90. # simply calls _get_dev_retry_helper from their get_dev, and implements
  91. # _inner_get_dev with their device acquisition logic. The NBD
  92. # implementation has an example.
  93. start_time = time.time()
  94. device = self._inner_get_dev()
  95. while not device:
  96. LOG.info(_('Device allocation failed. Will retry in 2 seconds.'))
  97. time.sleep(2)
  98. if time.time() - start_time > MAX_DEVICE_WAIT:
  99. LOG.warn(_('Device allocation failed after repeated retries.'))
  100. return False
  101. device = self._inner_get_dev()
  102. return True
  103. def _inner_get_dev(self):
  104. raise NotImplementedError()
  105. def unget_dev(self):
  106. """Release the block device from the file system namespace."""
  107. self.linked = False
  108. def map_dev(self):
  109. """Map partitions of the device to the file system namespace."""
  110. assert(os.path.exists(self.device))
  111. LOG.debug(_("Map dev %s"), self.device)
  112. automapped_path = '/dev/%sp%s' % (os.path.basename(self.device),
  113. self.partition)
  114. if self.partition == -1:
  115. self.error = _('partition search unsupported with %s') % self.mode
  116. elif self.partition and not os.path.exists(automapped_path):
  117. map_path = '/dev/mapper/%sp%s' % (os.path.basename(self.device),
  118. self.partition)
  119. assert(not os.path.exists(map_path))
  120. # Note kpartx can output warnings to stderr and succeed
  121. # Also it can output failures to stderr and "succeed"
  122. # So we just go on the existence of the mapped device
  123. _out, err = utils.trycmd('kpartx', '-a', self.device,
  124. run_as_root=True, discard_warnings=True)
  125. # Note kpartx does nothing when presented with a raw image,
  126. # so given we only use it when we expect a partitioned image, fail
  127. if not os.path.exists(map_path):
  128. if not err:
  129. err = _('partition %s not found') % self.partition
  130. self.error = _('Failed to map partitions: %s') % err
  131. else:
  132. self.mapped_device = map_path
  133. self.mapped = True
  134. elif self.partition and os.path.exists(automapped_path):
  135. # Note auto mapping can be enabled with the 'max_part' option
  136. # to the nbd or loop kernel modules. Beware of possible races
  137. # in the partition scanning for _loop_ devices though
  138. # (details in bug 1024586), which are currently uncatered for.
  139. self.mapped_device = automapped_path
  140. self.mapped = True
  141. self.automapped = True
  142. else:
  143. self.mapped_device = self.device
  144. self.mapped = True
  145. return self.mapped
  146. def unmap_dev(self):
  147. """Remove partitions of the device from the file system namespace."""
  148. if not self.mapped:
  149. return
  150. LOG.debug(_("Unmap dev %s"), self.device)
  151. if self.partition and not self.automapped:
  152. utils.execute('kpartx', '-d', self.device, run_as_root=True)
  153. self.mapped = False
  154. self.automapped = False
  155. def mnt_dev(self):
  156. """Mount the device into the file system."""
  157. LOG.debug(_("Mount %(dev)s on %(dir)s") %
  158. {'dev': self.mapped_device, 'dir': self.mount_dir})
  159. _out, err = utils.trycmd('mount', self.mapped_device, self.mount_dir,
  160. discard_warnings=True, run_as_root=True)
  161. if err:
  162. self.error = _('Failed to mount filesystem: %s') % err
  163. LOG.debug(self.error)
  164. return False
  165. self.mounted = True
  166. return True
  167. def unmnt_dev(self):
  168. """Unmount the device from the file system."""
  169. if not self.mounted:
  170. return
  171. LOG.debug(_("Umount %s") % self.mapped_device)
  172. utils.execute('umount', self.mapped_device, run_as_root=True)
  173. self.mounted = False
  174. def do_mount(self):
  175. """Call the get, map and mnt operations."""
  176. status = False
  177. try:
  178. status = self.get_dev() and self.map_dev() and self.mnt_dev()
  179. finally:
  180. if not status:
  181. LOG.debug(_("Fail to mount, tearing back down"))
  182. self.do_teardown()
  183. return status
  184. def do_umount(self):
  185. """Call the unmnt operation."""
  186. if self.mounted:
  187. self.unmnt_dev()
  188. def do_teardown(self):
  189. """Call the umnt, unmap, and unget operations."""
  190. if self.mounted:
  191. self.unmnt_dev()
  192. if self.mapped:
  193. self.unmap_dev()
  194. if self.linked:
  195. self.unget_dev()