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.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234
  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"),
  34. {'imgfile': imgfile, 'mountdir': mountdir,
  35. 'partition': partition, 'imgfmt': imgfmt})
  36. if imgfmt == "raw":
  37. LOG.debug(_("Using LoopMount"))
  38. return importutils.import_object(
  39. "nova.virt.disk.mount.loop.LoopMount",
  40. imgfile, mountdir, partition)
  41. else:
  42. LOG.debug(_("Using NbdMount"))
  43. return importutils.import_object(
  44. "nova.virt.disk.mount.nbd.NbdMount",
  45. imgfile, mountdir, partition)
  46. @staticmethod
  47. def instance_for_device(imgfile, mountdir, partition, device):
  48. LOG.debug(_("Instance for device imgfile=%(imgfile)s "
  49. "mountdir=%(mountdir)s partition=%(partition)s "
  50. "device=%(device)s"),
  51. {'imgfile': imgfile, 'mountdir': mountdir,
  52. 'partition': partition, 'device': device})
  53. if "loop" in device:
  54. LOG.debug(_("Using LoopMount"))
  55. return importutils.import_object(
  56. "nova.virt.disk.mount.loop.LoopMount",
  57. imgfile, mountdir, partition, device)
  58. else:
  59. LOG.debug(_("Using NbdMount"))
  60. return importutils.import_object(
  61. "nova.virt.disk.mount.nbd.NbdMount",
  62. imgfile, mountdir, partition, device)
  63. def __init__(self, image, mount_dir, partition=None, device=None):
  64. # Input
  65. self.image = image
  66. self.partition = partition
  67. self.mount_dir = mount_dir
  68. # Output
  69. self.error = ""
  70. # Internal
  71. self.linked = self.mapped = self.mounted = self.automapped = False
  72. self.device = self.mapped_device = device
  73. # Reset to mounted dir if possible
  74. self.reset_dev()
  75. def reset_dev(self):
  76. """Reset device paths to allow unmounting."""
  77. if not self.device:
  78. return
  79. self.linked = self.mapped = self.mounted = True
  80. device = self.device
  81. if os.path.isabs(device) and os.path.exists(device):
  82. if device.startswith('/dev/mapper/'):
  83. device = os.path.basename(device)
  84. device, self.partition = device.rsplit('p', 1)
  85. self.device = os.path.join('/dev', device)
  86. def get_dev(self):
  87. """Make the image available as a block device in the file system."""
  88. self.device = None
  89. self.linked = True
  90. return True
  91. def _get_dev_retry_helper(self):
  92. """Some implementations need to retry their get_dev."""
  93. # NOTE(mikal): This method helps implement retries. The implementation
  94. # simply calls _get_dev_retry_helper from their get_dev, and implements
  95. # _inner_get_dev with their device acquisition logic. The NBD
  96. # implementation has an example.
  97. start_time = time.time()
  98. device = self._inner_get_dev()
  99. while not device:
  100. LOG.info(_('Device allocation failed. Will retry in 2 seconds.'))
  101. time.sleep(2)
  102. if time.time() - start_time > MAX_DEVICE_WAIT:
  103. LOG.warn(_('Device allocation failed after repeated retries.'))
  104. return False
  105. device = self._inner_get_dev()
  106. return True
  107. def _inner_get_dev(self):
  108. raise NotImplementedError()
  109. def unget_dev(self):
  110. """Release the block device from the file system namespace."""
  111. self.linked = False
  112. def map_dev(self):
  113. """Map partitions of the device to the file system namespace."""
  114. assert(os.path.exists(self.device))
  115. LOG.debug(_("Map dev %s"), self.device)
  116. automapped_path = '/dev/%sp%s' % (os.path.basename(self.device),
  117. self.partition)
  118. if self.partition == -1:
  119. self.error = _('partition search unsupported with %s') % self.mode
  120. elif self.partition and not os.path.exists(automapped_path):
  121. map_path = '/dev/mapper/%sp%s' % (os.path.basename(self.device),
  122. self.partition)
  123. assert(not os.path.exists(map_path))
  124. # Note kpartx can output warnings to stderr and succeed
  125. # Also it can output failures to stderr and "succeed"
  126. # So we just go on the existence of the mapped device
  127. _out, err = utils.trycmd('kpartx', '-a', self.device,
  128. run_as_root=True, discard_warnings=True)
  129. # Note kpartx does nothing when presented with a raw image,
  130. # so given we only use it when we expect a partitioned image, fail
  131. if not os.path.exists(map_path):
  132. if not err:
  133. err = _('partition %s not found') % self.partition
  134. self.error = _('Failed to map partitions: %s') % err
  135. else:
  136. self.mapped_device = map_path
  137. self.mapped = True
  138. elif self.partition and os.path.exists(automapped_path):
  139. # Note auto mapping can be enabled with the 'max_part' option
  140. # to the nbd or loop kernel modules. Beware of possible races
  141. # in the partition scanning for _loop_ devices though
  142. # (details in bug 1024586), which are currently uncatered for.
  143. self.mapped_device = automapped_path
  144. self.mapped = True
  145. self.automapped = True
  146. else:
  147. self.mapped_device = self.device
  148. self.mapped = True
  149. return self.mapped
  150. def unmap_dev(self):
  151. """Remove partitions of the device from the file system namespace."""
  152. if not self.mapped:
  153. return
  154. LOG.debug(_("Unmap dev %s"), self.device)
  155. if self.partition and not self.automapped:
  156. utils.execute('kpartx', '-d', self.device, run_as_root=True)
  157. self.mapped = False
  158. self.automapped = False
  159. def mnt_dev(self):
  160. """Mount the device into the file system."""
  161. LOG.debug(_("Mount %(dev)s on %(dir)s") %
  162. {'dev': self.mapped_device, 'dir': self.mount_dir})
  163. _out, err = utils.trycmd('mount', self.mapped_device, self.mount_dir,
  164. discard_warnings=True, run_as_root=True)
  165. if err:
  166. self.error = _('Failed to mount filesystem: %s') % err
  167. LOG.debug(self.error)
  168. return False
  169. self.mounted = True
  170. return True
  171. def unmnt_dev(self):
  172. """Unmount the device from the file system."""
  173. if not self.mounted:
  174. return
  175. LOG.debug(_("Umount %s") % self.mapped_device)
  176. utils.execute('umount', self.mapped_device, run_as_root=True)
  177. self.mounted = False
  178. def do_mount(self):
  179. """Call the get, map and mnt operations."""
  180. status = False
  181. try:
  182. status = self.get_dev() and self.map_dev() and self.mnt_dev()
  183. finally:
  184. if not status:
  185. LOG.debug(_("Fail to mount, tearing back down"))
  186. self.do_teardown()
  187. return status
  188. def do_umount(self):
  189. """Call the unmnt operation."""
  190. if self.mounted:
  191. self.unmnt_dev()
  192. def do_teardown(self):
  193. """Call the umnt, unmap, and unget operations."""
  194. if self.mounted:
  195. self.unmnt_dev()
  196. if self.mapped:
  197. self.unmap_dev()
  198. if self.linked:
  199. self.unget_dev()