Revision 267520ba

b/lib/storage/filestorage.py
32 32
from ganeti import errors
33 33
from ganeti import pathutils
34 34
from ganeti import utils
35
from ganeti.utils import io
35 36
from ganeti.storage import base
36 37

  
37 38

  
39
class FileDeviceHelper(object):
40

  
41
  @classmethod
42
  def CreateFile(cls, path, size, create_folders=False,
43
                 _file_path_acceptance_fn=None):
44
    """Create a new file and its file device helper.
45

  
46
    @param size: the size in MiBs the file should be truncated to.
47
    @param create_folders: create the directories for the path if necessary
48
                           (using L{ganeti.utils.io.Makedirs})
49

  
50
    @rtype: FileDeviceHelper
51
    @return: The FileDeviceHelper object representing the object.
52
    @raise errors.FileStoragePathError: if the file path is disallowed by policy
53

  
54
    """
55

  
56
    if not _file_path_acceptance_fn:
57
      _file_path_acceptance_fn = CheckFileStoragePathAcceptance
58
    _file_path_acceptance_fn(path)
59

  
60
    if create_folders:
61
      folder = os.path.dirname(path)
62
      io.Makedirs(folder)
63

  
64
    try:
65
      fd = os.open(path, os.O_RDWR | os.O_CREAT | os.O_EXCL)
66
      f = os.fdopen(fd, "w")
67
      f.truncate(size * 1024 * 1024)
68
      f.close()
69
    except EnvironmentError as err:
70
      base.ThrowError("%s: can't create: %s", path, str(err))
71

  
72
    return FileDeviceHelper(path,
73
                            _file_path_acceptance_fn=_file_path_acceptance_fn)
74

  
75
  def __init__(self, path, _file_path_acceptance_fn=None):
76
    """Create a new file device helper.
77

  
78
    @raise errors.FileStoragePathError: if the file path is disallowed by policy
79

  
80
    """
81
    if not _file_path_acceptance_fn:
82
      _file_path_acceptance_fn = CheckFileStoragePathAcceptance
83
    _file_path_acceptance_fn(path)
84

  
85
    self.path = path
86

  
87
  def Exists(self, assert_exists=None):
88
    """Check for the existence of the given file.
89

  
90
    @param assert_exists: creates an assertion on the result value:
91
      - if true, raise errors.BlockDeviceError if the file doesn't exist
92
      - if false, raise errors.BlockDeviceError if the file does exist
93
    @rtype: boolean
94
    @return: True if the file exists
95

  
96
    """
97

  
98
    exists = os.path.isfile(self.path)
99

  
100
    if not exists and assert_exists is True:
101
      raise base.ThrowError("%s: No such file", self.path)
102
    if exists and assert_exists is False:
103
      raise base.ThrowError("%s: File exists", self.path)
104

  
105
    return exists
106

  
107
  def Remove(self):
108
    """Remove the file backing the block device.
109

  
110
    @rtype: boolean
111
    @return: True if the removal was successful
112

  
113
    """
114
    try:
115
      os.remove(self.path)
116
      return True
117
    except OSError as err:
118
      if err.errno != errno.ENOENT:
119
        base.ThrowError("%s: can't remove: %s", self.path, err)
120
      return False
121

  
122
  def Size(self):
123
    """Return the actual disk size in bytes.
124

  
125
    @rtype: int
126
    @return: The file size in bytes.
127

  
128
    """
129
    self.Exists(assert_exists=True)
130
    try:
131
      return os.stat(self.path).st_size
132
    except OSError as err:
133
      base.ThrowError("%s: can't stat: %s", self.path, err)
134

  
135
  def Grow(self, amount, dryrun, backingstore, _excl_stor):
136
    """Grow the file
137

  
138
    @param amount: the amount (in mebibytes) to grow by.
139

  
140
    """
141
    # Check that the file exists
142
    self.Exists(assert_exists=True)
143

  
144
    if amount < 0:
145
      base.ThrowError("%s: can't grow by negative amount", self.path)
146

  
147
    if dryrun:
148
      return
149
    if not backingstore:
150
      return
151

  
152
    current_size = self.Size()
153
    new_size = current_size + amount * 1024 * 1024
154
    try:
155
      f = open(self.path, "a+")
156
      f.truncate(new_size)
157
      f.close()
158
    except EnvironmentError, err:
159
      base.ThrowError("%s: can't grow: ", self.path, str(err))
160

  
161

  
38 162
class FileStorage(base.BlockDev):
39 163
  """File device.
40 164

  
......
55 179
      raise ValueError("Invalid configuration data %s" % str(unique_id))
56 180
    self.driver = unique_id[0]
57 181
    self.dev_path = unique_id[1]
58

  
59
    CheckFileStoragePathAcceptance(self.dev_path)
60

  
182
    self.file = FileDeviceHelper(self.dev_path)
61 183
    self.Attach()
62 184

  
63 185
  def Assemble(self):
......
66 188
    Checks whether the file device exists, raises BlockDeviceError otherwise.
67 189

  
68 190
    """
69
    if not os.path.exists(self.dev_path):
70
      base.ThrowError("File device '%s' does not exist" % self.dev_path)
191
    self.file.Exists(assert_exists=True)
71 192

  
72 193
  def Shutdown(self):
73 194
    """Shutdown the device.
......
101 222
    @return: True if the removal was successful
102 223

  
103 224
    """
104
    try:
105
      os.remove(self.dev_path)
106
    except OSError, err:
107
      if err.errno != errno.ENOENT:
108
        base.ThrowError("Can't remove file '%s': %s", self.dev_path, err)
225
    return self.file.Remove()
109 226

  
110 227
  def Rename(self, new_id):
111 228
    """Renames the file.
......
122 239
    """
123 240
    if not backingstore:
124 241
      return
125
    # Check that the file exists
126
    self.Assemble()
127
    current_size = self.GetActualSize()
128
    new_size = current_size + amount * 1024 * 1024
129
    assert new_size > current_size, "Cannot Grow with a negative amount"
130
    # We can't really simulate the growth
131 242
    if dryrun:
132 243
      return
133
    try:
134
      f = open(self.dev_path, "a+")
135
      f.truncate(new_size)
136
      f.close()
137
    except EnvironmentError, err:
138
      base.ThrowError("Error in file growth: %", str(err))
244
    self.file.Grow(amount, dryrun, backingstore, excl_stor)
139 245

  
140 246
  def Attach(self):
141 247
    """Attach to an existing file.
......
146 252
    @return: True if file exists
147 253

  
148 254
    """
149
    self.attached = os.path.exists(self.dev_path)
255
    self.attached = self.file.Exists()
150 256
    return self.attached
151 257

  
152 258
  def GetActualSize(self):
......
155 261
    @note: the device needs to be active when this is called
156 262

  
157 263
    """
158
    assert self.attached, "BlockDevice not attached in GetActualSize()"
159
    try:
160
      st = os.stat(self.dev_path)
161
      return st.st_size
162
    except OSError, err:
163
      base.ThrowError("Can't stat %s: %s", self.dev_path, err)
264
    return self.file.Size()
164 265

  
165 266
  @classmethod
166 267
  def Create(cls, unique_id, children, size, spindles, params, excl_stor,
......
182 283

  
183 284
    dev_path = unique_id[1]
184 285

  
185
    CheckFileStoragePathAcceptance(dev_path)
186

  
187
    try:
188
      fd = os.open(dev_path, os.O_RDWR | os.O_CREAT | os.O_EXCL)
189
      f = os.fdopen(fd, "w")
190
      f.truncate(size * 1024 * 1024)
191
      f.close()
192
    except EnvironmentError, err:
193
      if err.errno == errno.EEXIST:
194
        base.ThrowError("File already existing: %s", dev_path)
195
      base.ThrowError("Error in file creation: %", str(err))
196

  
286
    FileDeviceHelper.CreateFile(dev_path, size)
197 287
    return FileStorage(unique_id, children, size, params, dyn_params)
198 288

  
199 289

  
b/test/py/ganeti.storage.filestorage_unittest.py
28 28

  
29 29
from ganeti import errors
30 30
from ganeti.storage import filestorage
31
from ganeti.utils import io
31 32
from ganeti import utils
33
from ganeti import constants
32 34

  
33 35
import testutils
34 36

  
......
219 221
                      "/usr/lib64/xyz", _filename=tmpfile)
220 222

  
221 223

  
224
class TestFileDeviceHelper(testutils.GanetiTestCase):
225

  
226
  @staticmethod
227
  def _Make(path, create_with_size=None, create_folders=False):
228
    skip_checks = lambda path: None
229
    if create_with_size:
230
      return filestorage.FileDeviceHelper.Create(
231
        path, create_with_size, create_folders=create_folders,
232
        _file_path_acceptance_fn=skip_checks
233
      )
234
    else:
235
      return filestorage.FileDeviceHelper(path,
236
                                          _file_path_acceptance_fn=skip_checks)
237

  
238
  class TempEnvironment(object):
239

  
240
    def __init__(self, create_file=False, delete_file=True):
241
      self.create_file = create_file
242
      self.delete_file = delete_file
243

  
244
    def __enter__(self):
245
      self.directory = tempfile.mkdtemp()
246
      self.subdirectory = io.PathJoin(self.directory, "pinky")
247
      os.mkdir(self.subdirectory)
248
      self.path = io.PathJoin(self.subdirectory, "bunny")
249
      self.volume = TestFileDeviceHelper._Make(self.path)
250
      if self.create_file:
251
        open(self.path, mode="w").close()
252
      return self
253

  
254
    def __exit__(self, *args):
255
      if self.delete_file:
256
        os.unlink(self.path)
257
      os.rmdir(self.subdirectory)
258
      os.rmdir(self.directory)
259
      return False #don't swallow exceptions
260

  
261
  def testOperationsOnNonExistingFiles(self):
262
    path = "/e/no/ent"
263
    volume = TestFileDeviceHelper._Make(path)
264

  
265
    # These should fail horribly.
266
    volume.Exists(assert_exists=False)
267
    self.assertRaises(errors.BlockDeviceError, lambda: \
268
      volume.Exists(assert_exists=True))
269
    self.assertRaises(errors.BlockDeviceError, lambda: \
270
      volume.Size())
271
    self.assertRaises(errors.BlockDeviceError, lambda: \
272
      volume.Grow(0.020, True, False, None))
273

  
274
    # Removing however fails silently.
275
    volume.Remove()
276

  
277
    # Make sure we don't create all directories for you unless we ask for it
278
    self.assertRaises(errors.BlockDeviceError, lambda: \
279
      TestFileDeviceHelper._Make(path, create_with_size=1))
280

  
281
  def testFileCreation(self):
282
    with TestFileDeviceHelper.TempEnvironment() as env:
283
      TestFileDeviceHelper._Make(env.path, create_with_size=1)
284

  
285
      self.assertTrue(env.volume.Exists())
286
      env.volume.Exists(assert_exists=True)
287
      self.assertRaises(errors.BlockDeviceError, lambda: \
288
        env.volume.Exists(assert_exists=False))
289

  
290
    self.assertRaises(errors.BlockDeviceError, lambda: \
291
      TestFileDeviceHelper._Make("/enoent", create_with_size=0.042))
292

  
293
  def testFailSizeDirectory(self):
294
  # This should still fail.
295
   with TestFileDeviceHelper.TempEnvironment(delete_file=False) as env:
296
    self.assertRaises(errors.BlockDeviceError, lambda: \
297
      TestFileDeviceHelper._Make(env.subdirectory).Size())
298

  
299
  def testGrowFile(self):
300
    with TestFileDeviceHelper.TempEnvironment(create_file=True) as env:
301
      self.assertRaises(errors.BlockDeviceError, lambda: \
302
        env.volume.Grow(-1, False, True, None))
303

  
304
      env.volume.Grow(2, False, True, None)
305
      self.assertEqual(2.0, env.volume.Size() / 1024.0**2)
306

  
307
  def testRemoveFile(self):
308
    with TestFileDeviceHelper.TempEnvironment(create_file=True,
309
                                              delete_file=False) as env:
310
      env.volume.Remove()
311
      env.volume.Exists(assert_exists=False)
312

  
222 313
if __name__ == "__main__":
223 314
  testutils.GanetiTestProgram()

Also available in: Unified diff