Statistics
| Branch: | Tag: | Revision:

root / lib / storage / gluster.py @ ac156ecd

History | View | Annotate | Download (11.6 kB)

1 8106dd64 Santi Raffa
#
2 8106dd64 Santi Raffa
#
3 8106dd64 Santi Raffa
4 8106dd64 Santi Raffa
# Copyright (C) 2013 Google Inc.
5 8106dd64 Santi Raffa
#
6 8106dd64 Santi Raffa
# This program is free software; you can redistribute it and/or modify
7 8106dd64 Santi Raffa
# it under the terms of the GNU General Public License as published by
8 8106dd64 Santi Raffa
# the Free Software Foundation; either version 2 of the License, or
9 8106dd64 Santi Raffa
# (at your option) any later version.
10 8106dd64 Santi Raffa
#
11 8106dd64 Santi Raffa
# This program is distributed in the hope that it will be useful, but
12 8106dd64 Santi Raffa
# WITHOUT ANY WARRANTY; without even the implied warranty of
13 8106dd64 Santi Raffa
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
14 8106dd64 Santi Raffa
# General Public License for more details.
15 8106dd64 Santi Raffa
#
16 8106dd64 Santi Raffa
# You should have received a copy of the GNU General Public License
17 8106dd64 Santi Raffa
# along with this program; if not, write to the Free Software
18 8106dd64 Santi Raffa
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19 8106dd64 Santi Raffa
# 02110-1301, USA.
20 8106dd64 Santi Raffa
21 8106dd64 Santi Raffa
"""Gluster storage class.
22 8106dd64 Santi Raffa

23 8106dd64 Santi Raffa
This class is very similar to FileStorage, given that Gluster when mounted
24 8106dd64 Santi Raffa
behaves essentially like a regular file system. Unlike RBD, there are no
25 8106dd64 Santi Raffa
special provisions for block device abstractions (yet).
26 8106dd64 Santi Raffa

27 8106dd64 Santi Raffa
"""
28 58793040 Santi Raffa
import logging
29 58793040 Santi Raffa
import os
30 58793040 Santi Raffa
import socket
31 58793040 Santi Raffa
32 58793040 Santi Raffa
from ganeti import utils
33 8106dd64 Santi Raffa
from ganeti import errors
34 58793040 Santi Raffa
from ganeti import netutils
35 58793040 Santi Raffa
from ganeti import constants
36 8106dd64 Santi Raffa
37 58793040 Santi Raffa
from ganeti.utils import io
38 8106dd64 Santi Raffa
from ganeti.storage import base
39 8106dd64 Santi Raffa
from ganeti.storage.filestorage import FileDeviceHelper
40 8106dd64 Santi Raffa
41 8106dd64 Santi Raffa
42 58793040 Santi Raffa
class GlusterVolume(object):
43 58793040 Santi Raffa
  """This class represents a Gluster volume.
44 58793040 Santi Raffa

45 58793040 Santi Raffa
  Volumes are uniquely identified by:
46 58793040 Santi Raffa

47 58793040 Santi Raffa
    - their IP address
48 58793040 Santi Raffa
    - their port
49 58793040 Santi Raffa
    - the volume name itself
50 58793040 Santi Raffa

51 58793040 Santi Raffa
  Two GlusterVolume objects x, y with same IP address, port and volume name
52 58793040 Santi Raffa
  are considered equal.
53 58793040 Santi Raffa

54 58793040 Santi Raffa
  """
55 58793040 Santi Raffa
56 ac156ecd Santi Raffa
  def __init__(self, server_addr, port, volume, _run_cmd=utils.RunCmd,
57 ac156ecd Santi Raffa
               _mount_point=None):
58 58793040 Santi Raffa
    """Creates a Gluster volume object.
59 58793040 Santi Raffa

60 58793040 Santi Raffa
    @type server_addr: str
61 58793040 Santi Raffa
    @param server_addr: The address to connect to
62 58793040 Santi Raffa

63 58793040 Santi Raffa
    @type port: int
64 58793040 Santi Raffa
    @param port: The port to connect to (Gluster standard is 24007)
65 58793040 Santi Raffa

66 58793040 Santi Raffa
    @type volume: str
67 58793040 Santi Raffa
    @param volume: The gluster volume to use for storage.
68 58793040 Santi Raffa

69 58793040 Santi Raffa
    """
70 58793040 Santi Raffa
    self.server_addr = server_addr
71 58793040 Santi Raffa
    server_ip = netutils.Hostname.GetIP(self.server_addr)
72 58793040 Santi Raffa
    self._server_ip = server_ip
73 58793040 Santi Raffa
    port = netutils.ValidatePortNumber(port)
74 58793040 Santi Raffa
    self._port = port
75 58793040 Santi Raffa
    self._volume = volume
76 ac156ecd Santi Raffa
    if _mount_point: # tests
77 ac156ecd Santi Raffa
      self.mount_point = _mount_point
78 ac156ecd Santi Raffa
    else:
79 ac156ecd Santi Raffa
      self.mount_point = ssconf.SimpleStore().GetGlusterStorageDir()
80 58793040 Santi Raffa
81 58793040 Santi Raffa
    self._run_cmd = _run_cmd
82 58793040 Santi Raffa
83 58793040 Santi Raffa
  @property
84 58793040 Santi Raffa
  def server_ip(self):
85 58793040 Santi Raffa
    return self._server_ip
86 58793040 Santi Raffa
87 58793040 Santi Raffa
  @property
88 58793040 Santi Raffa
  def port(self):
89 58793040 Santi Raffa
    return self._port
90 58793040 Santi Raffa
91 58793040 Santi Raffa
  @property
92 58793040 Santi Raffa
  def volume(self):
93 58793040 Santi Raffa
    return self._volume
94 58793040 Santi Raffa
95 58793040 Santi Raffa
  def __eq__(self, other):
96 58793040 Santi Raffa
    return (self.server_ip, self.port, self.volume) == \
97 58793040 Santi Raffa
           (other.server_ip, other.port, other.volume)
98 58793040 Santi Raffa
99 58793040 Santi Raffa
  def __repr__(self):
100 58793040 Santi Raffa
    return """GlusterVolume("{ip}", {port}, "{volume}")""" \
101 58793040 Santi Raffa
             .format(ip=self.server_ip, port=self.port, volume=self.volume)
102 58793040 Santi Raffa
103 58793040 Santi Raffa
  def __hash__(self):
104 58793040 Santi Raffa
    return (self.server_ip, self.port, self.volume).__hash__()
105 58793040 Santi Raffa
106 58793040 Santi Raffa
  def _IsMounted(self):
107 58793040 Santi Raffa
    """Checks if we are mounted or not.
108 58793040 Santi Raffa

109 58793040 Santi Raffa
    @rtype: bool
110 58793040 Santi Raffa
    @return: True if this volume is mounted.
111 58793040 Santi Raffa

112 58793040 Santi Raffa
    """
113 58793040 Santi Raffa
    if not os.path.exists(self.mount_point):
114 58793040 Santi Raffa
      return False
115 58793040 Santi Raffa
116 58793040 Santi Raffa
    return os.path.ismount(self.mount_point)
117 58793040 Santi Raffa
118 58793040 Santi Raffa
  def _GuessMountFailReasons(self):
119 58793040 Santi Raffa
    """Try and give reasons why the mount might've failed.
120 58793040 Santi Raffa

121 58793040 Santi Raffa
    @rtype: str
122 58793040 Santi Raffa
    @return: A semicolon-separated list of problems found with the current setup
123 58793040 Santi Raffa
             suitable for display to the user.
124 58793040 Santi Raffa

125 58793040 Santi Raffa
    """
126 58793040 Santi Raffa
127 58793040 Santi Raffa
    reasons = []
128 58793040 Santi Raffa
129 58793040 Santi Raffa
    # Does the mount point exist?
130 58793040 Santi Raffa
    if not os.path.exists(self.mount_point):
131 58793040 Santi Raffa
      reasons.append("%r: does not exist" % self.mount_point)
132 58793040 Santi Raffa
133 58793040 Santi Raffa
    # Okay, it exists, but is it a directory?
134 58793040 Santi Raffa
    elif not os.path.isdir(self.mount_point):
135 58793040 Santi Raffa
      reasons.append("%r: not a directory" % self.mount_point)
136 58793040 Santi Raffa
137 58793040 Santi Raffa
    # If, for some unfortunate reason, this folder exists before mounting:
138 58793040 Santi Raffa
    #
139 58793040 Santi Raffa
    #   /var/run/ganeti/gluster/gv0/10.0.0.1:30000:gv0/
140 58793040 Santi Raffa
    #   '--------- cwd ------------'
141 58793040 Santi Raffa
    #
142 58793040 Santi Raffa
    # and you _are_ trying to mount the gluster volume gv0 on 10.0.0.1:30000,
143 58793040 Santi Raffa
    # then the mount.glusterfs command parser gets confused and this command:
144 58793040 Santi Raffa
    #
145 58793040 Santi Raffa
    #   mount -t glusterfs 10.0.0.1:30000:gv0 /var/run/ganeti/gluster/gv0
146 58793040 Santi Raffa
    #                      '-- remote end --' '------ mountpoint -------'
147 58793040 Santi Raffa
    #
148 58793040 Santi Raffa
    # gets parsed instead like this:
149 58793040 Santi Raffa
    #
150 58793040 Santi Raffa
    #   mount -t glusterfs 10.0.0.1:30000:gv0 /var/run/ganeti/gluster/gv0
151 58793040 Santi Raffa
    #                      '-- mountpoint --' '----- syntax error ------'
152 58793040 Santi Raffa
    #
153 58793040 Santi Raffa
    # and if there _is_ a gluster server running locally at the default remote
154 58793040 Santi Raffa
    # end, localhost:24007, then this is not a network error and therefore... no
155 58793040 Santi Raffa
    # usage message gets printed out. All you get is a Byson parser error in the
156 58793040 Santi Raffa
    # gluster log files about an unexpected token in line 1, "". (That's stdin.)
157 58793040 Santi Raffa
    #
158 58793040 Santi Raffa
    # Not that we rely on that output in any way whatsoever...
159 58793040 Santi Raffa
160 58793040 Santi Raffa
    parser_confusing = io.PathJoin(self.mount_point,
161 58793040 Santi Raffa
                                   self._GetFUSEMountString())
162 58793040 Santi Raffa
    if os.path.exists(parser_confusing):
163 58793040 Santi Raffa
      reasons.append("%r: please delete, rename or move." % parser_confusing)
164 58793040 Santi Raffa
165 58793040 Santi Raffa
    # Let's try something else: can we connect to the server?
166 58793040 Santi Raffa
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
167 58793040 Santi Raffa
    try:
168 58793040 Santi Raffa
      sock.connect((self.server_ip, self.port))
169 58793040 Santi Raffa
      sock.close()
170 58793040 Santi Raffa
    except socket.error as err:
171 58793040 Santi Raffa
      reasons.append("%s:%d: %s" % (self.server_ip, self.port, err.strerror))
172 58793040 Santi Raffa
173 58793040 Santi Raffa
    reasons.append("try running 'gluster volume info %s' on %s to ensure"
174 58793040 Santi Raffa
                   " it exists, it is started and it is using the tcp"
175 58793040 Santi Raffa
                   " transport" % (self.volume, self.server_ip))
176 58793040 Santi Raffa
177 58793040 Santi Raffa
    return "; ".join(reasons)
178 58793040 Santi Raffa
179 58793040 Santi Raffa
  def _GetFUSEMountString(self):
180 58793040 Santi Raffa
    """Return the string FUSE needs to mount this volume.
181 58793040 Santi Raffa

182 58793040 Santi Raffa
    @rtype: str
183 58793040 Santi Raffa
    """
184 58793040 Santi Raffa
185 58793040 Santi Raffa
    return "{ip}:{port}:{volume}" \
186 58793040 Santi Raffa
              .format(ip=self.server_ip, port=self.port, volume=self.volume)
187 58793040 Santi Raffa
188 58793040 Santi Raffa
  def GetKVMMountString(self, path):
189 58793040 Santi Raffa
    """Return the string KVM needs to use this volume.
190 58793040 Santi Raffa

191 58793040 Santi Raffa
    @rtype: str
192 58793040 Santi Raffa
    """
193 58793040 Santi Raffa
194 58793040 Santi Raffa
    ip = self.server_ip
195 58793040 Santi Raffa
    if netutils.IPAddress.GetAddressFamily(ip) == socket.AF_INET6:
196 58793040 Santi Raffa
      ip = "[%s]" % ip
197 58793040 Santi Raffa
    return "gluster://{ip}:{port}/{volume}/{path}" \
198 58793040 Santi Raffa
              .format(ip=ip, port=self.port, volume=self.volume, path=path)
199 58793040 Santi Raffa
200 58793040 Santi Raffa
  def Mount(self):
201 58793040 Santi Raffa
    """Try and mount the volume. No-op if the volume is already mounted.
202 58793040 Santi Raffa

203 58793040 Santi Raffa
    @raises BlockDeviceError: if the mount was unsuccessful
204 58793040 Santi Raffa

205 58793040 Santi Raffa
    @rtype: context manager
206 58793040 Santi Raffa
    @return: A simple context manager that lets you use this volume for
207 58793040 Santi Raffa
             short lived operations like so::
208 58793040 Santi Raffa

209 58793040 Santi Raffa
              with volume.mount():
210 58793040 Santi Raffa
                # Do operations on volume
211 58793040 Santi Raffa
              # Volume is now unmounted
212 58793040 Santi Raffa

213 58793040 Santi Raffa
    """
214 58793040 Santi Raffa
215 58793040 Santi Raffa
    class _GlusterVolumeContextManager(object):
216 58793040 Santi Raffa
217 58793040 Santi Raffa
      def __init__(self, volume):
218 58793040 Santi Raffa
        self.volume = volume
219 58793040 Santi Raffa
220 58793040 Santi Raffa
      def __enter__(self):
221 58793040 Santi Raffa
        # We're already mounted.
222 58793040 Santi Raffa
        return self
223 58793040 Santi Raffa
224 58793040 Santi Raffa
      def __exit__(self, *exception_information):
225 58793040 Santi Raffa
        self.volume.Unmount()
226 58793040 Santi Raffa
        return False # do not swallow exceptions.
227 58793040 Santi Raffa
228 58793040 Santi Raffa
    if self._IsMounted():
229 58793040 Santi Raffa
      return _GlusterVolumeContextManager(self)
230 58793040 Santi Raffa
231 58793040 Santi Raffa
    command = ["mount",
232 58793040 Santi Raffa
               "-t", "glusterfs",
233 58793040 Santi Raffa
               self._GetFUSEMountString(),
234 58793040 Santi Raffa
               self.mount_point]
235 58793040 Santi Raffa
236 58793040 Santi Raffa
    io.Makedirs(self.mount_point)
237 58793040 Santi Raffa
    self._run_cmd(" ".join(command),
238 58793040 Santi Raffa
                  # Why set cwd? Because it's an area we control. If,
239 58793040 Santi Raffa
                  # for some unfortunate reason, this folder exists:
240 58793040 Santi Raffa
                  #   "/%s/" % _GetFUSEMountString()
241 58793040 Santi Raffa
                  # ...then the gluster parser gets confused and treats
242 58793040 Santi Raffa
                  # _GetFUSEMountString() as your mount point and
243 58793040 Santi Raffa
                  # self.mount_point becomes a syntax error.
244 58793040 Santi Raffa
                  cwd=self.mount_point)
245 58793040 Santi Raffa
246 58793040 Santi Raffa
    # mount.glusterfs exits with code 0 even after failure.
247 58793040 Santi Raffa
    # https://bugzilla.redhat.com/show_bug.cgi?id=1031973
248 58793040 Santi Raffa
    if not self._IsMounted():
249 58793040 Santi Raffa
      reasons = self._GuessMountFailReasons()
250 58793040 Santi Raffa
      if not reasons:
251 58793040 Santi Raffa
        reasons = "%r failed." % (" ".join(command))
252 58793040 Santi Raffa
      base.ThrowError("%r: mount failure: %s",
253 58793040 Santi Raffa
                      self.mount_point,
254 58793040 Santi Raffa
                      reasons)
255 58793040 Santi Raffa
256 58793040 Santi Raffa
    return _GlusterVolumeContextManager(self)
257 58793040 Santi Raffa
258 58793040 Santi Raffa
  def Unmount(self):
259 58793040 Santi Raffa
    """Try and unmount the volume.
260 58793040 Santi Raffa

261 58793040 Santi Raffa
    Failures are logged but otherwise ignored.
262 58793040 Santi Raffa

263 58793040 Santi Raffa
    @raises BlockDeviceError: if the volume was not mounted to begin with.
264 58793040 Santi Raffa
    """
265 58793040 Santi Raffa
266 58793040 Santi Raffa
    if not self._IsMounted():
267 58793040 Santi Raffa
      base.ThrowError("%r: should be mounted but isn't.", self.mount_point)
268 58793040 Santi Raffa
269 58793040 Santi Raffa
    result = self._run_cmd(["umount",
270 58793040 Santi Raffa
                            self.mount_point])
271 58793040 Santi Raffa
272 58793040 Santi Raffa
    if result.failed:
273 58793040 Santi Raffa
      logging.warning("Failed to unmount %r from %r: %s",
274 58793040 Santi Raffa
                      self, self.mount_point, result.fail_reason)
275 58793040 Santi Raffa
276 58793040 Santi Raffa
277 8106dd64 Santi Raffa
class GlusterStorage(base.BlockDev):
278 8106dd64 Santi Raffa
  """File device using the Gluster backend.
279 8106dd64 Santi Raffa

280 8106dd64 Santi Raffa
  This class represents a file storage backend device stored on Gluster. The
281 8106dd64 Santi Raffa
  system administrator must mount the Gluster device himself at boot time before
282 8106dd64 Santi Raffa
  Ganeti is run.
283 8106dd64 Santi Raffa

284 8106dd64 Santi Raffa
  The unique_id for the file device is a (file_driver, file_path) tuple.
285 8106dd64 Santi Raffa

286 8106dd64 Santi Raffa
  """
287 8106dd64 Santi Raffa
  def __init__(self, unique_id, children, size, params, dyn_params):
288 8106dd64 Santi Raffa
    """Initalizes a file device backend.
289 8106dd64 Santi Raffa

290 8106dd64 Santi Raffa
    """
291 8106dd64 Santi Raffa
    if children:
292 8106dd64 Santi Raffa
      base.ThrowError("Invalid setup for file device")
293 8106dd64 Santi Raffa
    super(GlusterStorage, self).__init__(unique_id, children, size, params,
294 8106dd64 Santi Raffa
                                         dyn_params)
295 8106dd64 Santi Raffa
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
296 8106dd64 Santi Raffa
      raise ValueError("Invalid configuration data %s" % str(unique_id))
297 8106dd64 Santi Raffa
    self.driver = unique_id[0]
298 8106dd64 Santi Raffa
    self.dev_path = unique_id[1]
299 8106dd64 Santi Raffa
300 8106dd64 Santi Raffa
    self.file = FileDeviceHelper(self.dev_path)
301 8106dd64 Santi Raffa
302 8106dd64 Santi Raffa
    self.Attach()
303 8106dd64 Santi Raffa
304 8106dd64 Santi Raffa
  def Assemble(self):
305 8106dd64 Santi Raffa
    """Assemble the device.
306 8106dd64 Santi Raffa

307 8106dd64 Santi Raffa
    Checks whether the file device exists, raises BlockDeviceError otherwise.
308 8106dd64 Santi Raffa

309 8106dd64 Santi Raffa
    """
310 8106dd64 Santi Raffa
    assert self.attached, "Gluster file assembled without being attached"
311 8106dd64 Santi Raffa
    self.file.Exists(assert_exists=True)
312 8106dd64 Santi Raffa
313 8106dd64 Santi Raffa
  def Shutdown(self):
314 8106dd64 Santi Raffa
    """Shutdown the device.
315 8106dd64 Santi Raffa

316 8106dd64 Santi Raffa
    """
317 8106dd64 Santi Raffa
318 8106dd64 Santi Raffa
    self.file = None
319 8106dd64 Santi Raffa
    self.dev_path = None
320 8106dd64 Santi Raffa
    self.attached = False
321 8106dd64 Santi Raffa
322 8106dd64 Santi Raffa
  def Open(self, force=False):
323 8106dd64 Santi Raffa
    """Make the device ready for I/O.
324 8106dd64 Santi Raffa

325 8106dd64 Santi Raffa
    This is a no-op for the file type.
326 8106dd64 Santi Raffa

327 8106dd64 Santi Raffa
    """
328 8106dd64 Santi Raffa
    assert self.attached, "Gluster file opened without being attached"
329 8106dd64 Santi Raffa
330 8106dd64 Santi Raffa
  def Close(self):
331 8106dd64 Santi Raffa
    """Notifies that the device will no longer be used for I/O.
332 8106dd64 Santi Raffa

333 8106dd64 Santi Raffa
    This is a no-op for the file type.
334 8106dd64 Santi Raffa
    """
335 8106dd64 Santi Raffa
    pass
336 8106dd64 Santi Raffa
337 8106dd64 Santi Raffa
  def Remove(self):
338 8106dd64 Santi Raffa
    """Remove the file backing the block device.
339 8106dd64 Santi Raffa

340 8106dd64 Santi Raffa
    @rtype: boolean
341 8106dd64 Santi Raffa
    @return: True if the removal was successful
342 8106dd64 Santi Raffa

343 8106dd64 Santi Raffa
    """
344 8106dd64 Santi Raffa
    return self.file.Remove()
345 8106dd64 Santi Raffa
346 8106dd64 Santi Raffa
  def Rename(self, new_id):
347 8106dd64 Santi Raffa
    """Renames the file.
348 8106dd64 Santi Raffa

349 8106dd64 Santi Raffa
    """
350 8106dd64 Santi Raffa
    # TODO: implement rename for file-based storage
351 8106dd64 Santi Raffa
    base.ThrowError("Rename is not supported for Gluster storage")
352 8106dd64 Santi Raffa
353 8106dd64 Santi Raffa
  def Grow(self, amount, dryrun, backingstore, excl_stor):
354 8106dd64 Santi Raffa
    """Grow the file
355 8106dd64 Santi Raffa

356 8106dd64 Santi Raffa
    @param amount: the amount (in mebibytes) to grow with
357 8106dd64 Santi Raffa

358 8106dd64 Santi Raffa
    """
359 8106dd64 Santi Raffa
    self.file.Grow(amount, dryrun, backingstore, excl_stor)
360 8106dd64 Santi Raffa
361 8106dd64 Santi Raffa
  def Attach(self):
362 8106dd64 Santi Raffa
    """Attach to an existing file.
363 8106dd64 Santi Raffa

364 8106dd64 Santi Raffa
    Check if this file already exists.
365 8106dd64 Santi Raffa

366 8106dd64 Santi Raffa
    @rtype: boolean
367 8106dd64 Santi Raffa
    @return: True if file exists
368 8106dd64 Santi Raffa

369 8106dd64 Santi Raffa
    """
370 8106dd64 Santi Raffa
    self.attached = self.file.Exists()
371 8106dd64 Santi Raffa
    return self.attached
372 8106dd64 Santi Raffa
373 8106dd64 Santi Raffa
  def GetActualSize(self):
374 8106dd64 Santi Raffa
    """Return the actual disk size.
375 8106dd64 Santi Raffa

376 8106dd64 Santi Raffa
    @note: the device needs to be active when this is called
377 8106dd64 Santi Raffa

378 8106dd64 Santi Raffa
    """
379 8106dd64 Santi Raffa
    return self.file.Size()
380 8106dd64 Santi Raffa
381 8106dd64 Santi Raffa
  @classmethod
382 8106dd64 Santi Raffa
  def Create(cls, unique_id, children, size, spindles, params, excl_stor,
383 8106dd64 Santi Raffa
             dyn_params):
384 8106dd64 Santi Raffa
    """Create a new file.
385 8106dd64 Santi Raffa

386 8106dd64 Santi Raffa
    @param size: the size of file in MiB
387 8106dd64 Santi Raffa

388 8106dd64 Santi Raffa
    @rtype: L{bdev.FileStorage}
389 8106dd64 Santi Raffa
    @return: an instance of FileStorage
390 8106dd64 Santi Raffa

391 8106dd64 Santi Raffa
    """
392 8106dd64 Santi Raffa
    if excl_stor:
393 8106dd64 Santi Raffa
      raise errors.ProgrammerError("FileStorage device requested with"
394 8106dd64 Santi Raffa
                                   " exclusive_storage")
395 8106dd64 Santi Raffa
    if not isinstance(unique_id, (tuple, list)) or len(unique_id) != 2:
396 8106dd64 Santi Raffa
      raise ValueError("Invalid configuration data %s" % str(unique_id))
397 8106dd64 Santi Raffa
398 8106dd64 Santi Raffa
    dev_path = unique_id[1]
399 8106dd64 Santi Raffa
400 8106dd64 Santi Raffa
    FileDeviceHelper.Create(dev_path, size)
401 8106dd64 Santi Raffa
    return GlusterStorage(unique_id, children, size, params, dyn_params)